PageRenderTime 73ms CodeModel.GetById 35ms RepoModel.GetById 1ms app.codeStats 0ms

/static/scripts/mvc/history.js

https://bitbucket.org/james_taylor/galaxy-webapp-refactoring
JavaScript | 1129 lines | 763 code | 143 blank | 223 comment | 65 complexity | 75e26cdc00345510b1ecc6e252ac2e6e MD5 | raw file
  1. /*
  2. Backbone.js implementation of history panel
  3. TODO:
  4. replicate then refactor (could be the wrong order)
  5. fix:
  6. tags
  7. annotations
  8. _render_displayApps
  9. _render_downloadButton
  10. widget building (popupmenu, etc.)
  11. don't draw body until it's first unhide event
  12. all history.mako js -> this
  13. HIview state transitions (eg. upload -> ok), curr: build new, delete old, place new (in render)
  14. History (meta controls : collapse all, rename, annotate, etc. - see history.js.120823.bak)
  15. events (local/ui and otherwise)
  16. HistoryCollection: (collection of History: 'Saved Histories')
  17. ?move IconButtonViews -> templates?
  18. convert function comments to jsDoc style, complete comments
  19. collection -> show_deleted, show_hidden
  20. poly HistoryItemView on: for_editing, display_structured, trans.user
  21. incorporate relations?
  22. localization
  23. template helper {{#local}} calls _l()
  24. move inline styles into base.less
  25. add classes, ids on empty divs
  26. watch the magic strings
  27. */
  28. //==============================================================================
  29. //==============================================================================
  30. //TODO: move to Galaxy obj./namespace, decorate for current page (as GalaxyPaths)
  31. /*
  32. var Localizable = {
  33. localizedStrings : {},
  34. setLocalizedString : function( str, localizedString ){
  35. this.localizedStrings[ str ] = localizedString;
  36. },
  37. localize : function( str ){
  38. if( str in this.localizedStrings ){ return this.localizedStrings[ str ]; }
  39. return str;
  40. }
  41. };
  42. var LocalizableView = LoggingView.extend( Localizable );
  43. */
  44. //TODO: wire up to views
  45. //==============================================================================
  46. // jq plugin?
  47. //?? into template? I dunno: need to handle variadic keys, remove empty attrs (href="")
  48. //TODO: not happy with this (a 4th rendering/templating system!?) or it being global
  49. function linkHTMLTemplate( config, tag ){
  50. // Create an anchor (or any tag) using any config params passed in
  51. //NOTE!: send class attr as 'classes' to avoid res. keyword collision (jsLint)
  52. if( !config ){ return '<a></a>'; }
  53. tag = tag || 'a';
  54. var template = [ '<' + tag ];
  55. for( key in config ){
  56. var val = config[ key ];
  57. if( val === '' ){ continue; }
  58. switch( key ){
  59. case 'text': continue;
  60. case 'classes':
  61. // handle keyword class which is also an HTML attr name
  62. key = 'class';
  63. val = ( config.classes.join )?( config.classes.join( ' ' ) ):( config.classes );
  64. //note: lack of break (fall through)
  65. default:
  66. template.push( [ ' ', key, '="', val, '"' ].join( '' ) );
  67. }
  68. }
  69. template.push( '>' );
  70. if( 'text' in config ){ template.push( config.text ); }
  71. template.push( '</' + tag + '>' );
  72. return template.join( '' );
  73. }
  74. //==============================================================================
  75. //TODO: use initialize (or validate) to check purged AND deleted -> purged XOR deleted
  76. var HistoryItem = BaseModel.extend( LoggableMixin ).extend({
  77. // a single HDA model
  78. // uncomment this out see log messages
  79. //logger : console,
  80. defaults : {
  81. id : null,
  82. name : '',
  83. data_type : null,
  84. file_size : 0,
  85. genome_build : null,
  86. metadata_data_lines : 0,
  87. metadata_dbkey : null,
  88. metadata_sequences : 0,
  89. misc_blurb : '',
  90. misc_info : '',
  91. model_class : '',
  92. state : '',
  93. deleted : false,
  94. purged : false,
  95. // clash with BaseModel here?
  96. visible : true,
  97. for_editing : true,
  98. // additional urls will be passed and added, if permissions allow their use
  99. bodyIsShown : false
  100. },
  101. initialize : function(){
  102. this.log( this + '.initialize', this.attributes );
  103. this.log( '\tparent history_id: ' + this.get( 'history_id' ) );
  104. //TODO: accessible is set in alt_hist
  105. // this state is not in trans.app.model.Dataset.states - set it here
  106. if( !this.get( 'accessible' ) ){
  107. this.set( 'state', HistoryItem.STATES.NOT_VIEWABLE );
  108. }
  109. },
  110. isEditable : function(){
  111. // roughly can_edit from history_common.mako - not deleted or purged = editable
  112. return (
  113. //this.get( 'for_editing' )
  114. //&& !( this.get( 'deleted' ) || this.get( 'purged' ) )
  115. !( this.get( 'deleted' ) || this.get( 'purged' ) )
  116. );
  117. },
  118. hasData : function(){
  119. //TODO:?? is this equivalent to all possible hda.has_data calls?
  120. return ( this.get( 'file_size' ) > 0 );
  121. },
  122. toString : function(){
  123. var nameAndId = this.get( 'id' ) || '';
  124. if( this.get( 'name' ) ){
  125. nameAndId += ':"' + this.get( 'name' ) + '"';
  126. }
  127. return 'HistoryItem(' + nameAndId + ')';
  128. }
  129. });
  130. //------------------------------------------------------------------------------
  131. HistoryItem.STATES = {
  132. NOT_VIEWABLE : 'not_viewable', // not in trans.app.model.Dataset.states
  133. NEW : 'new',
  134. UPLOAD : 'upload',
  135. QUEUED : 'queued',
  136. RUNNING : 'running',
  137. OK : 'ok',
  138. EMPTY : 'empty',
  139. ERROR : 'error',
  140. DISCARDED : 'discarded',
  141. SETTING_METADATA : 'setting_metadata',
  142. FAILED_METADATA : 'failed_metadata'
  143. };
  144. //==============================================================================
  145. var HistoryItemView = BaseView.extend( LoggableMixin ).extend({
  146. //??TODO: add alias in initialize this.hda = this.model?
  147. // view for HistoryItem model above
  148. // uncomment this out see log messages
  149. //logger : console,
  150. tagName : "div",
  151. className : "historyItemContainer",
  152. // ................................................................................ SET UP
  153. initialize : function(){
  154. this.log( this + '.initialize:', this, this.model );
  155. },
  156. // ................................................................................ RENDER MAIN
  157. //??: this style builds an entire, new DOM tree - is that what we want??
  158. render : function(){
  159. var id = this.model.get( 'id' ),
  160. state = this.model.get( 'state' );
  161. this.clearReferences();
  162. this.$el.attr( 'id', 'historyItemContainer-' + id );
  163. var itemWrapper = $( '<div/>' ).attr( 'id', 'historyItem-' + id )
  164. .addClass( 'historyItemWrapper' ).addClass( 'historyItem' )
  165. .addClass( 'historyItem-' + state );
  166. itemWrapper.append( this._render_warnings() );
  167. itemWrapper.append( this._render_titleBar() );
  168. this.body = $( this._render_body() );
  169. itemWrapper.append( this.body );
  170. // set up canned behavior on children (bootstrap, popupmenus, editable_text, etc.)
  171. itemWrapper.find( '.tooltip' ).tooltip({ placement : 'bottom' });
  172. //TODO: broken
  173. var popupmenus = itemWrapper.find( '[popupmenu]' );
  174. popupmenus.each( function( i, menu ){
  175. menu = $( menu );
  176. make_popupmenu( menu );
  177. });
  178. //TODO: better transition/method than this...
  179. this.$el.children().remove();
  180. return this.$el.append( itemWrapper );
  181. },
  182. clearReferences : function(){
  183. //??TODO: best way?
  184. //?? do we really need these - not so far
  185. this.displayButton = null;
  186. this.editButton = null;
  187. this.deleteButton = null;
  188. this.errButton = null;
  189. },
  190. // ................................................................................ RENDER WARNINGS
  191. _render_warnings : function(){
  192. // jQ errs on building dom with whitespace - if there are no messages, trim -> ''
  193. return $( jQuery.trim( HistoryItemView.templates.messages( this.model.toJSON() ) ) );
  194. },
  195. // ................................................................................ RENDER TITLEBAR
  196. _render_titleBar : function(){
  197. var titleBar = $( '<div class="historyItemTitleBar" style="overflow: hidden"></div>' );
  198. titleBar.append( this._render_titleButtons() );
  199. titleBar.append( '<span class="state-icon"></span>' );
  200. titleBar.append( this._render_titleLink() );
  201. return titleBar;
  202. },
  203. // ................................................................................ display, edit attr, delete
  204. _render_titleButtons : function(){
  205. // render the display, edit attr and delete icon-buttons
  206. var buttonDiv = $( '<div class="historyItemButtons"></div>' );
  207. buttonDiv.append( this._render_displayButton() );
  208. buttonDiv.append( this._render_editButton() );
  209. buttonDiv.append( this._render_deleteButton() );
  210. return buttonDiv;
  211. },
  212. _render_displayButton : function(){
  213. // don't show display while uploading
  214. if( this.model.get( 'state' ) === HistoryItem.STATES.UPLOAD ){ return null; }
  215. // show a disabled display if the data's been purged
  216. displayBtnData = ( this.model.get( 'purged' ) )?({
  217. title : 'Cannot display datasets removed from disk',
  218. enabled : false,
  219. icon_class : 'display'
  220. // if not, render the display icon-button with href
  221. }):({
  222. title : 'Display data in browser',
  223. href : this.model.get( 'display_url' ),
  224. target : ( this.model.get( 'for_editing' ) )?( 'galaxy_main' ):( null ),
  225. icon_class : 'display'
  226. });
  227. this.displayButton = new IconButtonView({ model : new IconButton( displayBtnData ) });
  228. return this.displayButton.render().$el;
  229. },
  230. _render_editButton : function(){
  231. // don't show edit while uploading, or if editable
  232. if( ( this.model.get( 'state' ) === HistoryItem.STATES.UPLOAD )
  233. || ( !this.model.get( 'for_editing' ) ) ){
  234. return null;
  235. }
  236. var purged = this.model.get( 'purged' ),
  237. deleted = this.model.get( 'deleted' ),
  238. editBtnData = {
  239. title : 'Edit attributes',
  240. href : this.model.get( 'edit_url' ),
  241. target : 'galaxy_main',
  242. icon_class : 'edit'
  243. };
  244. // disable if purged or deleted and explain why in the tooltip
  245. //TODO: if for_editing
  246. if( deleted || purged ){
  247. editBtnData.enabled = false;
  248. }
  249. if( deleted ){
  250. editBtnData.title = 'Undelete dataset to edit attributes';
  251. } else if( purged ){
  252. editBtnData.title = 'Cannot edit attributes of datasets removed from disk';
  253. }
  254. this.editButton = new IconButtonView({ model : new IconButton( editBtnData ) });
  255. return this.editButton.render().$el;
  256. },
  257. _render_deleteButton : function(){
  258. // don't show delete if not editable
  259. if( !this.model.get( 'for_editing' ) ){ return null; }
  260. var deleteBtnData = {
  261. title : 'Delete',
  262. href : this.model.get( 'delete_url' ),
  263. target : 'galaxy_main',
  264. id : 'historyItemDeleter-' + this.model.get( 'id' ),
  265. icon_class : 'delete'
  266. };
  267. if( ( this.model.get( 'deleted' ) || this.model.get( 'purged' ) )
  268. && ( !this.model.get( 'delete_url' ) ) ){
  269. deleteBtnData = {
  270. title : 'Dataset is already deleted',
  271. icon_class : 'delete',
  272. enabled : false
  273. };
  274. }
  275. this.deleteButton = new IconButtonView({ model : new IconButton( deleteBtnData ) });
  276. return this.deleteButton.render().$el;
  277. },
  278. // ................................................................................ titleLink
  279. _render_titleLink : function(){
  280. return $( jQuery.trim( HistoryItemView.templates.titleLink( this.model.toJSON() ) ) );
  281. },
  282. // ................................................................................ RENDER BODY
  283. _render_hdaSummary : function(){
  284. var modelData = this.model.toJSON();
  285. // if there's no dbkey and it's editable : pass a flag to the template to render a link to editing in the '?'
  286. if( this.model.get( 'metadata_dbkey' ) === '?'
  287. && this.model.isEditable() ){
  288. _.extend( modelData, { dbkey_unknown_and_editable : true });
  289. }
  290. return HistoryItemView.templates.hdaSummary( modelData );
  291. },
  292. // ................................................................................ primary actions
  293. _render_primaryActionButtons : function( buttonRenderingFuncs ){
  294. var primaryActionButtons = $( '<div/>' ),
  295. view = this;
  296. _.each( buttonRenderingFuncs, function( fn ){
  297. primaryActionButtons.append( fn.call( view ) );
  298. });
  299. return primaryActionButtons;
  300. },
  301. _render_downloadButton : function(){
  302. // return either: a single download icon-button (if there are no meta files)
  303. // or a popupmenu with links to download assoc. meta files (if there are meta files)
  304. // don't show anything if the data's been purged
  305. if( this.model.get( 'purged' ) ){ return null; }
  306. var downloadLink = linkHTMLTemplate({
  307. title : 'Download',
  308. href : this.model.get( 'download_url' ),
  309. classes : [ 'icon-button', 'tooltip', 'disk' ]
  310. });
  311. // if no metafiles, return only the main download link
  312. var download_meta_urls = this.model.get( 'download_meta_urls' );
  313. if( !download_meta_urls ){
  314. return downloadLink;
  315. }
  316. // build the popupmenu for downloading main, meta files
  317. var popupmenu = $( '<div popupmenu="dataset-' + this.model.get( 'id' ) + '-popup"></div>' );
  318. popupmenu.append( linkHTMLTemplate({
  319. text : 'Download Dataset',
  320. title : 'Download',
  321. href : this.model.get( 'download_url' ),
  322. classes : [ 'icon-button', 'tooltip', 'disk' ]
  323. }));
  324. popupmenu.append( '<a>Additional Files</a>' );
  325. for( file_type in download_meta_urls ){
  326. popupmenu.append( linkHTMLTemplate({
  327. text : 'Download ' + file_type,
  328. href : download_meta_urls[ file_type ],
  329. classes : [ 'action-button' ]
  330. }));
  331. }
  332. var menuButton = $( ( '<div style="float:left;" class="menubutton split popup"'
  333. + ' id="dataset-${dataset_id}-popup"></div>' ) );
  334. menuButton.append( downloadLink );
  335. popupmenu.append( menuButton );
  336. return popupmenu;
  337. },
  338. //NOTE: button renderers have the side effect of caching their IconButtonViews to this view
  339. _render_errButton : function(){
  340. if( ( this.model.get( 'state' ) !== HistoryItem.STATES.ERROR )
  341. || ( !this.model.get( 'for_editing' ) ) ){ return null; }
  342. this.errButton = new IconButtonView({ model : new IconButton({
  343. title : 'View or report this error',
  344. href : this.model.get( 'report_error_url' ),
  345. target : 'galaxy_main',
  346. icon_class : 'bug'
  347. })});
  348. return this.errButton.render().$el;
  349. },
  350. _render_showParamsButton : function(){
  351. // gen. safe to show in all cases
  352. this.showParamsButton = new IconButtonView({ model : new IconButton({
  353. title : 'View details',
  354. href : this.model.get( 'show_params_url' ),
  355. target : 'galaxy_main',
  356. icon_class : 'information'
  357. }) });
  358. return this.showParamsButton.render().$el;
  359. },
  360. _render_rerunButton : function(){
  361. if( !this.model.get( 'for_editing' ) ){ return null; }
  362. this.rerunButton = new IconButtonView({ model : new IconButton({
  363. title : 'Run this job again',
  364. href : this.model.get( 'rerun_url' ),
  365. target : 'galaxy_main',
  366. icon_class : 'arrow-circle'
  367. }) });
  368. return this.rerunButton.render().$el;
  369. },
  370. _render_tracksterButton : function(){
  371. var trackster_urls = this.model.get( 'trackster_urls' );
  372. if( !( this.model.hasData() )
  373. || !( this.model.get( 'for_editing' ) )
  374. || !( trackster_urls ) ){ return null; }
  375. this.tracksterButton = new IconButtonView({ model : new IconButton({
  376. title : 'View in Trackster',
  377. icon_class : 'chart_curve'
  378. })});
  379. this.errButton.render(); //?? needed?
  380. this.errButton.$el.addClass( 'trackster-add' ).attr({
  381. 'data-url' : trackster_urls[ 'data-url' ],
  382. 'action-url': trackster_urls[ 'action-url' ],
  383. 'new-url' : trackster_urls[ 'new-url' ]
  384. });
  385. return this.errButton.$el;
  386. },
  387. // ................................................................................ secondary actions
  388. _render_secondaryActionButtons : function( buttonRenderingFuncs ){
  389. // move to the right (same level as primary)
  390. var secondaryActionButtons = $( '<div style="float: right;"></div>' ),
  391. view = this;
  392. _.each( buttonRenderingFuncs, function( fn ){
  393. secondaryActionButtons.append( fn.call( view ) );
  394. });
  395. return secondaryActionButtons;
  396. },
  397. _render_tagButton : function(){
  398. if( !( this.model.hasData() )
  399. || !( this.model.get( 'for_editing' ) )
  400. || ( !this.model.get( 'retag_url' ) ) ){ return null; }
  401. this.tagButton = new IconButtonView({ model : new IconButton({
  402. title : 'Edit dataset tags',
  403. target : 'galaxy_main',
  404. href : this.model.get( 'retag_url' ),
  405. icon_class : 'tags'
  406. })});
  407. return this.tagButton.render().$el;
  408. },
  409. _render_annotateButton : function(){
  410. if( !( this.model.hasData() )
  411. || !( this.model.get( 'for_editing' ) )
  412. || ( !this.model.get( 'annotate_url' ) ) ){ return null; }
  413. this.annotateButton = new IconButtonView({ model : new IconButton({
  414. title : 'Edit dataset annotation',
  415. target : 'galaxy_main',
  416. href : this.model.get( 'annotate_url' ),
  417. icon_class : 'annotate'
  418. })});
  419. return this.annotateButton.render().$el;
  420. },
  421. // ................................................................................ other elements
  422. _render_tagArea : function(){
  423. if( this.model.get( 'retag_url' ) ){ return null; }
  424. //TODO: move to mvc/tags.js
  425. return $( HistoryItemView.templates.tagArea( this.model.toJSON() ) );
  426. },
  427. _render_annotationArea : function(){
  428. if( !this.model.get( 'annotate_url' ) ){ return null; }
  429. //TODO: move to mvc/annotations.js
  430. return $( HistoryItemView.templates.annotationArea( this.model.toJSON() ) );
  431. },
  432. _render_displayApps : function(){
  433. if( !this.model.get( 'display_apps' ) ){ return null; }
  434. var displayApps = this.model.get( 'displayApps' ),
  435. displayAppsDiv = $( '<div/>' ),
  436. displayAppSpan = $( '<span/>' );
  437. this.log( this + 'displayApps:', displayApps );
  438. ////TODO: grrr...somethings not in the right scope here
  439. //for( app_name in displayApps ){
  440. // //TODO: to template
  441. // var display_app = displayApps[ app_name ],
  442. // display_app_HTML = app_name + ' ';
  443. // for( location_name in display_app ){
  444. // display_app_HTML += linkHTMLTemplate({
  445. // text : location_name,
  446. // href : display_app[ location_name ].url,
  447. // target : display_app[ location_name ].target
  448. // }) + ' ';
  449. // }
  450. // display_app_span.append( display_app_HTML );
  451. //}
  452. //displayAppsDiv.append( display_app_span );
  453. //displayAppsDiv.append( '<br />' );
  454. //var display_appsDiv = $( '<div/>' );
  455. //if( this.model.get( 'display_apps' ) ){
  456. //
  457. // var display_apps = this.model.get( 'display_apps' ),
  458. // display_app_span = $( '<span/>' );
  459. //
  460. // //TODO: grrr...somethings not in the right scope here
  461. // for( app_name in display_apps ){
  462. // //TODO: to template
  463. // var display_app = display_apps[ app_name ],
  464. // display_app_HTML = app_name + ' ';
  465. // for( location_name in display_app ){
  466. // display_app_HTML += linkHTMLTemplate({
  467. // text : location_name,
  468. // href : display_app[ location_name ].url,
  469. // target : display_app[ location_name ].target
  470. // }) + ' ';
  471. // }
  472. // display_app_span.append( display_app_HTML );
  473. // }
  474. // display_appsDiv.append( display_app_span );
  475. //}
  476. ////display_appsDiv.append( '<br />' );
  477. //parent.append( display_appsDiv );
  478. return displayAppsDiv;
  479. },
  480. _render_peek : function(){
  481. if( !this.model.get( 'peek' ) ){ return null; }
  482. return $( '<div/>' ).append(
  483. $( '<pre/>' )
  484. .attr( 'id', 'peek' + this.model.get( 'id' ) )
  485. .addClass( 'peek' )
  486. .append( this.model.get( 'peek' ) )
  487. );
  488. },
  489. // ................................................................................ state body renderers
  490. // _render_body fns for the various states
  491. _render_body_not_viewable : function( parent ){
  492. //TODO: revisit - still showing display, edit, delete (as common) - that CAN'T be right
  493. parent.append( $( '<div>You do not have permission to view dataset.</div>' ) );
  494. },
  495. _render_body_uploading : function( parent ){
  496. parent.append( $( '<div>Dataset is uploading</div>' ) );
  497. },
  498. _render_body_queued : function( parent ){
  499. parent.append( $( '<div>Job is waiting to run.</div>' ) );
  500. parent.append( this._render_primaryActionButtons([
  501. this._render_showParamsButton,
  502. this._render_rerunButton
  503. ]));
  504. },
  505. _render_body_running : function( parent ){
  506. parent.append( '<div>Job is currently running.</div>' );
  507. parent.append( this._render_primaryActionButtons([
  508. this._render_showParamsButton,
  509. this._render_rerunButton
  510. ]));
  511. },
  512. _render_body_error : function( parent ){
  513. if( !this.model.get( 'purged' ) ){
  514. parent.append( $( '<div>' + this.model.get( 'misc_blurb' ) + '</div>' ) );
  515. }
  516. parent.append( ( 'An error occurred running this job: '
  517. + '<i>' + $.trim( this.model.get( 'misc_info' ) ) + '</i>' ) );
  518. parent.append( this._render_primaryActionButtons([
  519. this._render_downloadButton,
  520. this._render_errButton,
  521. this._render_showParamsButton,
  522. this._render_rerunButton
  523. ]));
  524. },
  525. _render_body_discarded : function( parent ){
  526. parent.append( '<div>The job creating this dataset was cancelled before completion.</div>' );
  527. parent.append( this._render_primaryActionButtons([
  528. this._render_showParamsButton,
  529. this._render_rerunButton
  530. ]));
  531. },
  532. _render_body_setting_metadata : function( parent ){
  533. parent.append( $( '<div>Metadata is being auto-detected.</div>' ) );
  534. },
  535. _render_body_empty : function( parent ){
  536. //TODO: replace i with dataset-misc-info class
  537. //?? why are we showing the file size when we know it's zero??
  538. parent.append( $( '<div>No data: <i>' + this.model.get( 'misc_blurb' ) + '</i></div>' ) );
  539. parent.append( this._render_primaryActionButtons([
  540. this._render_showParamsButton,
  541. this._render_rerunButton
  542. ]));
  543. },
  544. _render_body_failed_metadata : function( parent ){
  545. //TODO: the css for this box is broken (unlike the others)
  546. // add a message box about the failure at the top of the body...
  547. parent.append( $( HistoryItemView.templates.failedMetadata( this.model.toJSON() ) ) );
  548. //...then render the remaining body as STATES.OK (only diff between these states is the box above)
  549. this._render_body_ok( parent );
  550. },
  551. _render_body_ok : function( parent ){
  552. // most common state renderer and the most complicated
  553. parent.append( this._render_hdaSummary() );
  554. parent.append( this._render_primaryActionButtons([
  555. this._render_downloadButton,
  556. this._render_errButton,
  557. this._render_showParamsButton,
  558. this._render_rerunButton
  559. ]));
  560. parent.append( this._render_secondaryActionButtons([
  561. this._render_tagButton,
  562. this._render_annotateButton
  563. ]));
  564. parent.append( '<div class="clear"/>' );
  565. parent.append( this._render_tagArea() );
  566. parent.append( this._render_annotationArea() );
  567. parent.append( this._render_displayApps() );
  568. parent.append( this._render_peek() );
  569. },
  570. _render_body : function(){
  571. //this.log( this + '_render_body' );
  572. var state = this.model.get( 'state' );
  573. //this.log( 'state:', state, 'for_editing', for_editing );
  574. //TODO: incorrect id (encoded - use hid?)
  575. var body = $( '<div/>' )
  576. .attr( 'id', 'info-' + this.model.get( 'id' ) )
  577. .addClass( 'historyItemBody' )
  578. .attr( 'style', 'display: block' );
  579. //TODO: not a fan of this
  580. switch( state ){
  581. case HistoryItem.STATES.NOT_VIEWABLE :
  582. this._render_body_not_viewable( body );
  583. break;
  584. case HistoryItem.STATES.UPLOAD :
  585. this._render_body_uploading( body );
  586. break;
  587. case HistoryItem.STATES.QUEUED :
  588. this._render_body_queued( body );
  589. break;
  590. case HistoryItem.STATES.RUNNING :
  591. this._render_body_running( body );
  592. break;
  593. case HistoryItem.STATES.ERROR :
  594. this._render_body_error( body );
  595. break;
  596. case HistoryItem.STATES.DISCARDED :
  597. this._render_body_discarded( body );
  598. break;
  599. case HistoryItem.STATES.SETTING_METADATA :
  600. this._render_body_setting_metadata( body );
  601. break;
  602. case HistoryItem.STATES.EMPTY :
  603. this._render_body_empty( body );
  604. break;
  605. case HistoryItem.STATES.FAILED_METADATA :
  606. this._render_body_failed_metadata( body );
  607. break;
  608. case HistoryItem.STATES.OK :
  609. this._render_body_ok( body );
  610. break;
  611. default:
  612. //??: no body?
  613. body.append( $( '<div>Error: unknown dataset state "' + state + '".</div>' ) );
  614. }
  615. body.append( '<div style="clear: both"></div>' );
  616. if( this.model.get( 'bodyIsShown' ) === false ){
  617. body.hide();
  618. }
  619. return body;
  620. },
  621. // ................................................................................ EVENTS
  622. events : {
  623. 'click .historyItemTitle' : 'toggleBodyVisibility',
  624. 'click a.icon-button.tags' : 'loadAndDisplayTags',
  625. 'click a.icon-button.annotate' : 'loadAndDisplayAnnotation'
  626. },
  627. // ................................................................................ STATE CHANGES / MANIPULATION
  628. loadAndDisplayTags : function( event ){
  629. //BUG: broken with latest
  630. //TODO: this is a drop in from history.mako - should use MV as well
  631. this.log( this + '.loadAndDisplayTags', event );
  632. var tagArea = this.$el.find( '.tag-area' ),
  633. tagElt = tagArea.find( '.tag-elt' );
  634. // Show or hide tag area; if showing tag area and it's empty, fill it.
  635. if( tagArea.is( ":hidden" ) ){
  636. if( !tagElt.html() ){
  637. // Need to fill tag element.
  638. $.ajax({
  639. url: this.model.get( 'ajax_get_tag_url' ),
  640. error: function() { alert( "Tagging failed" ); },
  641. success: function(tag_elt_html) {
  642. tagElt.html(tag_elt_html);
  643. tagElt.find(".tooltip").tooltip();
  644. tagArea.slideDown("fast");
  645. }
  646. });
  647. } else {
  648. // Tag element is filled; show.
  649. tagArea.slideDown("fast");
  650. }
  651. } else {
  652. // Hide.
  653. tagArea.slideUp("fast");
  654. }
  655. return false;
  656. },
  657. loadAndDisplayAnnotation : function( event ){
  658. //BUG: broken with latest
  659. //TODO: this is a drop in from history.mako - should use MV as well
  660. this.log( this + '.loadAndDisplayAnnotation', event );
  661. var annotationArea = this.$el.find( '.annotation-area' ),
  662. annotationElem = annotationArea.find( '.annotation-elt' ),
  663. setAnnotationUrl = this.model.get( 'ajax_set_annotation_url' );
  664. // Show or hide annotation area; if showing annotation area and it's empty, fill it.
  665. if ( annotationArea.is( ":hidden" ) ){
  666. if( !annotationElem.html() ){
  667. // Need to fill annotation element.
  668. $.ajax({
  669. url: this.model.get( 'ajax_get_annotation_url' ),
  670. error: function(){ alert( "Annotations failed" ); },
  671. success: function( htmlFromAjax ){
  672. if( htmlFromAjax === "" ){
  673. htmlFromAjax = "<em>Describe or add notes to dataset</em>";
  674. }
  675. annotationElem.html( htmlFromAjax );
  676. annotationArea.find(".tooltip").tooltip();
  677. async_save_text(
  678. annotationElem.attr("id"), annotationElem.attr("id"),
  679. setAnnotationUrl,
  680. "new_annotation", 18, true, 4
  681. );
  682. annotationArea.slideDown("fast");
  683. }
  684. });
  685. } else {
  686. annotationArea.slideDown("fast");
  687. }
  688. } else {
  689. // Hide.
  690. annotationArea.slideUp("fast");
  691. }
  692. return false;
  693. },
  694. toggleBodyVisibility : function(){
  695. this.log( this + '.toggleBodyVisibility' );
  696. this.$el.find( '.historyItemBody' ).toggle();
  697. },
  698. // ................................................................................ UTILTIY
  699. toString : function(){
  700. var modelString = ( this.model )?( this.model + '' ):( '' );
  701. return 'HistoryItemView(' + modelString + ')';
  702. }
  703. });
  704. //------------------------------------------------------------------------------
  705. //HistoryItemView.templates = InDomTemplateLoader.getTemplates({
  706. HistoryItemView.templates = CompiledTemplateLoader.getTemplates({
  707. 'common-templates.html' : {
  708. warningMsg : 'template-warningmessagesmall'
  709. },
  710. 'history-templates.html' : {
  711. messages : 'template-history-warning-messages',
  712. titleLink : 'template-history-titleLink',
  713. hdaSummary : 'template-history-hdaSummary',
  714. failedMetadata : 'template-history-failedMetaData',
  715. tagArea : 'template-history-tagArea',
  716. annotationArea : 'template-history-annotationArea'
  717. }
  718. });
  719. //==============================================================================
  720. var HistoryCollection = Backbone.Collection.extend({
  721. model : HistoryItem,
  722. toString : function(){
  723. return ( 'HistoryCollection()' );
  724. }
  725. });
  726. //==============================================================================
  727. var History = BaseModel.extend( LoggableMixin ).extend({
  728. // uncomment this out see log messages
  729. //logger : console,
  730. // values from api (may need more)
  731. defaults : {
  732. id : '',
  733. name : '',
  734. state : '',
  735. state_details : {
  736. discarded : 0,
  737. empty : 0,
  738. error : 0,
  739. failed_metadata : 0,
  740. ok : 0,
  741. queued : 0,
  742. running : 0,
  743. setting_metadata: 0,
  744. upload : 0
  745. }
  746. },
  747. initialize : function( data, history_datasets ){
  748. this.log( this + '.initialize', data, history_datasets );
  749. this.items = new HistoryCollection();
  750. },
  751. loadDatasetsAsHistoryItems : function( datasets ){
  752. // adds the given dataset/Item data to historyItems
  753. // and updates this.state based on their states
  754. //pre: datasets is a list of objs
  755. //this.log( this + '.loadDatasets', datasets );
  756. var self = this,
  757. selfID = this.get( 'id' ),
  758. stateDetails = this.get( 'state_details' );
  759. _.each( datasets, function( dataset, index ){
  760. self.log( 'loading dataset: ', dataset, index );
  761. // create an item sending along the history_id as well
  762. var historyItem = new HistoryItem(
  763. _.extend( dataset, { history_id: selfID } ) );
  764. self.log( 'as History:', historyItem );
  765. self.items.add( historyItem );
  766. // add item's state to running totals in stateDetails
  767. var itemState = dataset.state;
  768. stateDetails[ itemState ] += 1;
  769. });
  770. // get overall History state from totals
  771. this.set( 'state_details', stateDetails );
  772. this._stateFromStateDetails();
  773. return this;
  774. },
  775. _stateFromStateDetails : function(){
  776. // sets this.state based on current historyItems' states
  777. // ported from api/histories.traverse
  778. //pre: state_details is current counts of dataset/item states
  779. this.set( 'state', '' );
  780. var stateDetails = this.get( 'state_details' );
  781. //TODO: make this more concise
  782. if( ( stateDetails.error > 0 )
  783. || ( stateDetails.failed_metadata > 0 ) ){
  784. this.set( 'state', HistoryItem.STATES.ERROR );
  785. } else if( ( stateDetails.running > 0 )
  786. || ( stateDetails.setting_metadata > 0 ) ){
  787. this.set( 'state', HistoryItem.STATES.RUNNING );
  788. } else if( stateDetails.queued > 0 ){
  789. this.set( 'state', HistoryItem.STATES.QUEUED );
  790. } else if( stateDetails.ok === this.items.length ){
  791. this.set( 'state', HistoryItem.STATES.OK );
  792. } else {
  793. throw( '_stateFromStateDetails: unable to determine '
  794. + 'history state from state details: ' + this.state_details );
  795. }
  796. return this;
  797. },
  798. toString : function(){
  799. var nameString = ( this.get( 'name' ) )?
  800. ( ',' + this.get( 'name' ) ) : ( '' );
  801. return 'History(' + this.get( 'id' ) + nameString + ')';
  802. }
  803. });
  804. //------------------------------------------------------------------------------
  805. var HistoryView = BaseView.extend( LoggableMixin ).extend({
  806. // view for the HistoryCollection (as per current right hand panel)
  807. // uncomment this out see log messages
  808. //logger : console,
  809. // direct attachment to existing element
  810. el : 'body.historyPage',
  811. initialize : function(){
  812. this.log( this + '.initialize' );
  813. this.itemViews = [];
  814. var parent = this;
  815. this.model.items.each( function( item ){
  816. var itemView = new HistoryItemView({ model: item });
  817. parent.itemViews.push( itemView );
  818. });
  819. //itemViews.reverse();
  820. },
  821. render : function(){
  822. this.log( this + '.render' );
  823. // render to temp, move all at once, remove temp holder
  824. //NOTE!: render in reverse (newest on top) via prepend (instead of append)
  825. var tempDiv = $( '<div/>' );
  826. _.each( this.itemViews, function( view ){
  827. tempDiv.prepend( view.render() );
  828. });
  829. this.$el.append( tempDiv.children() );
  830. tempDiv.remove();
  831. },
  832. toString : function(){
  833. var nameString = this.model.get( 'name' ) || '';
  834. return 'HistoryView(' + nameString + ')';
  835. }
  836. });
  837. //==============================================================================
  838. function createMockHistoryData(){
  839. mockHistory = {};
  840. mockHistory.data = {
  841. template : {
  842. id : 'a799d38679e985db',
  843. name : 'template',
  844. data_type : 'fastq',
  845. file_size : 226297533,
  846. genome_build : '?',
  847. metadata_data_lines : 0,
  848. metadata_dbkey : '?',
  849. metadata_sequences : 0,
  850. misc_blurb : '215.8 MB',
  851. misc_info : 'uploaded fastq file (misc_info)',
  852. model_class : 'HistoryDatasetAssociation',
  853. download_url : '',
  854. state : 'ok',
  855. visible : true,
  856. deleted : false,
  857. purged : false,
  858. hid : 0,
  859. //TODO: move to history
  860. for_editing : true,
  861. //for_editing : false,
  862. //?? not needed
  863. //can_edit : true,
  864. //can_edit : false,
  865. accessible : true,
  866. //TODO: move into model functions (build there (and cache?))
  867. //!! be careful with adding these accrd. to permissions
  868. //!! IOW, don't send them via template/API if the user doesn't have perms to use
  869. //!! (even if they don't show up)
  870. undelete_url : '',
  871. purge_url : '',
  872. unhide_url : '',
  873. display_url : 'example.com/display',
  874. edit_url : 'example.com/edit',
  875. delete_url : 'example.com/delete',
  876. show_params_url : 'example.com/show_params',
  877. rerun_url : 'example.com/rerun',
  878. retag_url : 'example.com/retag',
  879. annotate_url : 'example.com/annotate',
  880. peek : [
  881. '<table cellspacing="0" cellpadding="3"><tr><th>1.QNAME</th><th>2.FLAG</th><th>3.RNAME</th><th>4.POS</th><th>5.MAPQ</th><th>6.CIGAR</th><th>7.MRNM</th><th>8.MPOS</th><th>9.ISIZE</th><th>10.SEQ</th><th>11.QUAL</th><th>12.OPT</th></tr>',
  882. '<tr><td colspan="100%">@SQ SN:gi|87159884|ref|NC_007793.1| LN:2872769</td></tr>',
  883. '<tr><td colspan="100%">@PG ID:bwa PN:bwa VN:0.5.9-r16</td></tr>',
  884. '<tr><td colspan="100%">HWUSI-EAS664L:15:64HOJAAXX:1:1:13280:968 73 gi|87159884|ref|NC_007793.1| 2720169 37 101M = 2720169 0 NAATATGACATTATTTTCAAAACAGCTGAAAATTTAGACGTACCGATTTATCTACATCCCGCGCCAGTTAACAGTGACATTTATCAATCATACTATAAAGG !!!!!!!!!!$!!!$!!!!!$!!!!!!$!$!$$$!!$!!$!!!!!!!!!!!$!</td></tr>',
  885. '<tr><td colspan="100%">!!!$!$!$$!!$$!!$!!!!!!!!!!!!!!!!!!!!!!!!!!$!!$!! XT:A:U NM:i:1 SM:i:37 AM:i:0 X0:i:1 X1:i:0 XM:i:1 XO:i:0 XG:i:0 MD:Z:0A100</td></tr>',
  886. '<tr><td colspan="100%">HWUSI-EAS664L:15:64HOJAAXX:1:1:13280:968 133 gi|87159884|ref|NC_007793.1| 2720169 0 * = 2720169 0 NAAACTGTGGCTTCGTTNNNNNNNNNNNNNNNGTGANNNNNNNNNNNNNNNNNNNGNNNNNNNNNNNNNNNNNNNNCNAANNNNNNNNNNNNNNNNNNNNN !!!!!!!!!!!!$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</td></tr>',
  887. '<tr><td colspan="100%">!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</td></tr>',
  888. '</table>'
  889. ].join( '' )
  890. }
  891. };
  892. _.extend( mockHistory.data, {
  893. notAccessible :
  894. _.extend( _.clone( mockHistory.data.template ),
  895. { accessible : false }),
  896. //deleted, purged, visible
  897. deleted :
  898. _.extend( _.clone( mockHistory.data.template ),
  899. { deleted : true,
  900. delete_url : '',
  901. purge_url : 'example.com/purge',
  902. undelete_url : 'example.com/undelete' }),
  903. purgedNotDeleted :
  904. _.extend( _.clone( mockHistory.data.template ),
  905. { purged : true,
  906. delete_url : '' }),
  907. notvisible :
  908. _.extend( _.clone( mockHistory.data.template ),
  909. { visible : false,
  910. unhide_url : 'example.com/unhide' }),
  911. hasDisplayApps :
  912. _.extend( _.clone( mockHistory.data.template ),
  913. { display_apps : {
  914. 'display in IGB' : {
  915. Web: "/display_application/63cd3858d057a6d1/igb_bam/Web",
  916. Local: "/display_application/63cd3858d057a6d1/igb_bam/Local"
  917. }
  918. }
  919. }
  920. ),
  921. canTrackster :
  922. _.extend( _.clone( mockHistory.data.template ),
  923. { trackster_urls : {
  924. 'data-url' : "example.com/trackster-data",
  925. 'action-url' : "example.com/trackster-action",
  926. 'new-url' : "example.com/trackster-new"
  927. }
  928. }
  929. ),
  930. zeroSize :
  931. _.extend( _.clone( mockHistory.data.template ),
  932. { file_size : 0 }),
  933. hasMetafiles :
  934. _.extend( _.clone( mockHistory.data.template ), {
  935. download_meta_urls : {
  936. 'bam_index' : "example.com/bam-index"
  937. }
  938. }),
  939. //states
  940. upload :
  941. _.extend( _.clone( mockHistory.data.template ),
  942. { state : HistoryItem.STATES.UPLOAD }),
  943. queued :
  944. _.extend( _.clone( mockHistory.data.template ),
  945. { state : HistoryItem.STATES.QUEUED }),
  946. running :
  947. _.extend( _.clone( mockHistory.data.template ),
  948. { state : HistoryItem.STATES.RUNNING }),
  949. empty :
  950. _.extend( _.clone( mockHistory.data.template ),
  951. { state : HistoryItem.STATES.EMPTY }),
  952. error :
  953. _.extend( _.clone( mockHistory.data.template ),
  954. { state : HistoryItem.STATES.ERROR,
  955. report_error_url: 'example.com/report_err' }),
  956. discarded :
  957. _.extend( _.clone( mockHistory.data.template ),
  958. { state : HistoryItem.STATES.DISCARDED }),
  959. setting_metadata :
  960. _.extend( _.clone( mockHistory.data.template ),
  961. { state : HistoryItem.STATES.SETTING_METADATA }),
  962. failed_metadata :
  963. _.extend( _.clone( mockHistory.data.template ),
  964. { state : HistoryItem.STATES.FAILED_METADATA })
  965. /*
  966. */
  967. });
  968. $( document ).ready( function(){
  969. //mockHistory.views.deleted.logger = console;
  970. mockHistory.items = {};
  971. mockHistory.views = {};
  972. for( key in mockHistory.data ){
  973. mockHistory.items[ key ] = new HistoryItem( mockHistory.data[ key ] );
  974. mockHistory.items[ key ].set( 'name', key );
  975. mockHistory.views[ key ] = new HistoryItemView({ model : mockHistory.items[ key ] });
  976. //console.debug( 'view: ', mockHistory.views[ key ] );
  977. $( 'body' ).append( mockHistory.views[ key ].render() );
  978. }
  979. });
  980. }