PageRenderTime 45ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/static/scripts/mvc/collection/collection-panel.js

https://bitbucket.org/kellrott/galaxy-central
JavaScript | 390 lines | 241 code | 46 blank | 103 comment | 28 complexity | fdcf8b38e60f7092a2de92b44fb78098 MD5 | raw file
Possible License(s): CC-BY-3.0
  1. define([
  2. "mvc/collection/collection-model",
  3. "mvc/collection/collection-li",
  4. "mvc/base-mvc",
  5. "utils/localization"
  6. ], function( DC_MODEL, DC_LI, BASE_MVC, _l ){
  7. /* =============================================================================
  8. TODO:
  9. ============================================================================= */
  10. // =============================================================================
  11. /** @class non-editable, read-only View/Controller for a dataset collection.
  12. */
  13. var CollectionPanel = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend(
  14. /** @lends CollectionPanel.prototype */{
  15. //MODEL is either a DatasetCollection (or subclass) or a DatasetCollectionElement (list of pairs)
  16. /** logger used to record this.log messages, commonly set to console */
  17. //logger : console,
  18. tagName : 'div',
  19. className : 'dataset-collection-panel',
  20. /** (in ms) that jquery effects will use */
  21. fxSpeed : 'fast',
  22. /** sub view class used for datasets */
  23. DatasetDCEViewClass : DC_LI.DatasetDCEListItemView,
  24. /** sub view class used for nested collections */
  25. NestedDCDCEViewClass : DC_LI.NestedDCDCEListItemView,
  26. // ......................................................................... SET UP
  27. /** Set up the view, set up storage, bind listeners to HistoryContents events
  28. * @param {Object} attributes optional settings for the panel
  29. */
  30. initialize : function( attributes ){
  31. attributes = attributes || {};
  32. // set the logger if requested
  33. if( attributes.logger ){
  34. this.logger = attributes.logger;
  35. }
  36. this.log( this + '.initialize:', attributes );
  37. this.linkTarget = attributes.linkTarget || '_blank';
  38. this.hasUser = attributes.hasUser;
  39. this.panelStack = [];
  40. this.parentName = attributes.parentName;
  41. //window.collectionPanel = this;
  42. },
  43. /** create any event listeners for the panel
  44. * @fires: rendered:initial on the first render
  45. * @fires: empty-history when switching to a history with no HDAs or creating a new history
  46. */
  47. _setUpListeners : function(){
  48. // debugging
  49. //if( this.logger ){
  50. this.on( 'all', function( event ){
  51. this.log( this + '', arguments );
  52. }, this );
  53. //}
  54. return this;
  55. },
  56. // ------------------------------------------------------------------------ history/hda event listening
  57. /** listening for history and HDA events */
  58. _setUpModelEventHandlers : function(){
  59. return this;
  60. },
  61. // ------------------------------------------------------------------------ panel rendering
  62. /** Render panel
  63. * @fires: rendered when the panel is attached and fully visible
  64. * @see Backbone.View#render
  65. */
  66. render : function( speed, callback ){
  67. this.log( 'render:', speed, callback );
  68. // send a speed of 0 to have no fade in/out performed
  69. speed = ( speed === undefined )?( this.fxSpeed ):( speed );
  70. //this.debug( this + '.render, fxSpeed:', speed );
  71. var panel = this,
  72. $newRender;
  73. // handle the possibility of no model (can occur if fetching the model returns an error)
  74. if( !this.model ){
  75. return this;
  76. }
  77. $newRender = this.renderModel();
  78. // fade out existing, swap with the new, fade in, set up behaviours
  79. $( panel ).queue( 'fx', [
  80. function( next ){
  81. if( speed && panel.$el.is( ':visible' ) ){
  82. panel.$el.fadeOut( speed, next );
  83. } else {
  84. next();
  85. }
  86. },
  87. function( next ){
  88. // swap over from temp div newRender
  89. panel.$el.empty();
  90. if( $newRender ){
  91. panel.$el.append( $newRender.children() );
  92. }
  93. next();
  94. },
  95. function( next ){
  96. if( speed && !panel.$el.is( ':visible' ) ){
  97. panel.$el.fadeIn( speed, next );
  98. } else {
  99. next();
  100. }
  101. },
  102. function( next ){
  103. //TODO: ideally, these would be set up before the fade in (can't because of async save text)
  104. if( callback ){ callback.call( this ); }
  105. panel.trigger( 'rendered', this );
  106. next();
  107. }
  108. ]);
  109. return this;
  110. },
  111. /** render with collection data
  112. * @returns {jQuery} dom fragment as temporary container to be swapped out later
  113. */
  114. renderModel : function( ){
  115. // tmp div for final swap in render
  116. //TODO: ugh - reuse issue - refactor out
  117. var type = this.model.get( 'collection_type' ) || this.model.object.get( 'collection_type' ),
  118. json = _.extend( this.model.toJSON(), {
  119. parentName : this.parentName,
  120. type : type
  121. }),
  122. $newRender = $( '<div/>' ).append( this.templates.panel( json ) );
  123. this._setUpBehaviours( $newRender );
  124. this.renderContents( $newRender );
  125. return $newRender;
  126. },
  127. /** Set up js/widget behaviours */
  128. _setUpBehaviours : function( $where ){
  129. //TODO: these should be either sub-MVs, or handled by events
  130. $where = $where || this.$el;
  131. $where.find( '[title]' ).tooltip({ placement: 'bottom' });
  132. return this;
  133. },
  134. // ------------------------------------------------------------------------ sub-$element shortcuts
  135. /** the scroll container for this panel - can be $el, $el.parent(), or grandparent depending on context */
  136. $container : function(){
  137. return ( this.findContainerFn )?( this.findContainerFn.call( this ) ):( this.$el.parent() );
  138. },
  139. /** where list content views are attached */
  140. $datasetsList : function( $where ){
  141. return ( $where || this.$el ).find( '.datasets-list' );
  142. },
  143. // ------------------------------------------------------------------------ sub-views
  144. /** Set up/render a view for each DCE to be shown, init with model and listeners.
  145. * DCE views are cached to the map this.contentViews (using the model.id as key).
  146. * @param {jQuery} $whereTo what dom element to prepend the DCE views to
  147. * @returns the number of visible DCE views
  148. */
  149. renderContents : function( $whereTo ){
  150. //this.debug( 'renderContents, elements:', this.model.elements );
  151. $whereTo = $whereTo || this.$el;
  152. this.warn( this + '.renderContents:, model:', this.model );
  153. var panel = this,
  154. contentViews = {},
  155. //NOTE: no filtering here
  156. visibleContents = this.model.getVisibleContents();
  157. //this.debug( 'renderContents, visibleContents:', visibleContents, $whereTo );
  158. this.$datasetsList( $whereTo ).empty();
  159. if( visibleContents && visibleContents.length ){
  160. visibleContents.each( function( content ){
  161. var contentId = content.id,
  162. contentView = panel._createContentView( content );
  163. contentViews[ contentId ] = contentView;
  164. panel._attachContentView( contentView.render(), $whereTo );
  165. });
  166. }
  167. this.contentViews = contentViews;
  168. return this.contentViews;
  169. },
  170. /** */
  171. _createContentView : function( content ){
  172. //this.debug( 'content json:', JSON.stringify( content, null, ' ' ) );
  173. var contentView = null,
  174. ContentClass = this._getContentClass( content );
  175. //this.debug( 'content:', content );
  176. //this.debug( 'ContentClass:', ContentClass );
  177. contentView = new ContentClass({
  178. model : content,
  179. linkTarget : this.linkTarget,
  180. //draggable : true,
  181. hasUser : this.hasUser,
  182. logger : this.logger
  183. });
  184. //this.debug( 'contentView:', contentView );
  185. this._setUpContentListeners( contentView );
  186. return contentView;
  187. },
  188. /** */
  189. _getContentClass : function( content ){
  190. //this.debug( this + '._getContentClass:', content );
  191. //TODO: subclasses use DCEViewClass - but are currently unused - decide
  192. switch( content.get( 'element_type' ) ){
  193. case 'hda':
  194. return this.DatasetDCEViewClass;
  195. case 'dataset_collection':
  196. return this.NestedDCDCEViewClass;
  197. }
  198. throw new TypeError( 'Unknown element type:', content.get( 'element_type' ) );
  199. },
  200. /** Set up listeners for content view events. In this override, handle collection expansion. */
  201. _setUpContentListeners : function( contentView ){
  202. var panel = this;
  203. if( contentView.model.get( 'element_type' ) === 'dataset_collection' ){
  204. contentView.on( 'expanded', function( collectionView ){
  205. panel.info( 'expanded', collectionView );
  206. panel._addCollectionPanel( collectionView );
  207. });
  208. }
  209. },
  210. /** When a sub-collection is clicked, hide the current panel and render the sub-collection in its own panel */
  211. _addCollectionPanel : function( collectionView ){
  212. //TODO: a bit hackish
  213. var currPanel = this,
  214. collectionModel = collectionView.model;
  215. //this.debug( 'collection panel (stack), collectionView:', collectionView );
  216. //this.debug( 'collection panel (stack), collectionModel:', collectionModel );
  217. var panel = new PairCollectionPanel({
  218. model : collectionModel,
  219. parentName : this.model.get( 'name' ),
  220. linkTarget : this.linkTarget
  221. });
  222. currPanel.panelStack.push( panel );
  223. currPanel.$( '.controls' ).add( '.datasets-list' ).hide();
  224. currPanel.$el.append( panel.$el );
  225. panel.on( 'close', function(){
  226. currPanel.render();
  227. collectionView.collapse();
  228. currPanel.panelStack.pop();
  229. });
  230. //TODO: to hdca-model, hasDetails
  231. if( !panel.model.hasDetails() ){
  232. var xhr = panel.model.fetch();
  233. xhr.done( function(){
  234. //TODO: (re-)render collection contents
  235. panel.render();
  236. });
  237. } else {
  238. panel.render();
  239. }
  240. },
  241. /** attach an contentView to the panel */
  242. _attachContentView : function( contentView, $whereTo ){
  243. $whereTo = $whereTo || this.$el;
  244. var $datasetsList = this.$datasetsList( $whereTo );
  245. $datasetsList.append( contentView.$el );
  246. return this;
  247. },
  248. // ------------------------------------------------------------------------ panel events
  249. /** event map */
  250. events : {
  251. 'click .navigation .back' : 'close'
  252. },
  253. /** close/remove this collection panel */
  254. close : function( event ){
  255. this.$el.remove();
  256. this.trigger( 'close' );
  257. },
  258. // ........................................................................ misc
  259. /** string rep */
  260. toString : function(){
  261. return 'CollectionPanel(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';
  262. }
  263. });
  264. //----------------------------------------------------------------------------- TEMPLATES
  265. /** underscore templates */
  266. CollectionPanel.templates = CollectionPanel.prototype.templates = (function(){
  267. // use closure to run underscore template fn only once at module load
  268. var _panelTemplate = _.template([
  269. '<div class="controls">',
  270. '<div class="navigation">',
  271. '<a class="back" href="javascript:void(0)">',
  272. '<span class="fa fa-icon fa-angle-left"></span>',
  273. _l( 'Back to ' ), '<%- collection.parentName %>',
  274. '</a>',
  275. '</div>',
  276. '<div class="title">',
  277. '<div class="name"><%- collection.name || collection.element_identifier %></div>',
  278. '<div class="subtitle">',
  279. //TODO: remove logic from template
  280. '<% if( collection.type === "list" ){ %>',
  281. _l( 'a list of datasets' ),
  282. '<% } else if( collection.type === "paired" ){ %>',
  283. _l( 'a pair of datasets' ),
  284. '<% } else if( collection.type === "list:paired" ){ %>',
  285. _l( 'a list of paired datasets' ),
  286. '<% } %>',
  287. '</div>',
  288. '</div>',
  289. '</div>',
  290. // where the datasets/hdas are added
  291. '<div class="datasets-list"></div>'
  292. ].join( '' ));
  293. // we override here in order to pass the localizer (_L) into the template scope - since we use it as a fn within
  294. return {
  295. panel : function( json ){
  296. return _panelTemplate({ _l: _l, collection: json });
  297. }
  298. };
  299. }());
  300. // =============================================================================
  301. /** @class non-editable, read-only View/Controller for a dataset collection. */
  302. var ListCollectionPanel = CollectionPanel.extend(
  303. /** @lends ListCollectionPanel.prototype */{
  304. //TODO: not strictly needed - due to switch in CollectionPanel._getContentClass
  305. /** sub view class used for datasets */
  306. DatasetDCEViewClass : DC_LI.DatasetDCEListItemView,
  307. // ........................................................................ misc
  308. /** string rep */
  309. toString : function(){
  310. return 'ListCollectionPanel(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';
  311. }
  312. });
  313. // =============================================================================
  314. /** @class non-editable, read-only View/Controller for a dataset collection. */
  315. var PairCollectionPanel = ListCollectionPanel.extend(
  316. /** @lends PairCollectionPanel.prototype */{
  317. // ........................................................................ misc
  318. /** string rep */
  319. toString : function(){
  320. return 'PairCollectionPanel(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';
  321. }
  322. });
  323. // =============================================================================
  324. /** @class non-editable, read-only View/Controller for a dataset collection. */
  325. var ListOfPairsCollectionPanel = CollectionPanel.extend(
  326. /** @lends ListOfPairsCollectionPanel.prototype */{
  327. //TODO: not strictly needed - due to switch in CollectionPanel._getContentClass
  328. /** sub view class used for nested collections */
  329. NestedDCDCEViewClass : DC_LI.NestedDCDCEListItemView,
  330. // ........................................................................ misc
  331. /** string rep */
  332. toString : function(){
  333. return 'ListOfPairsCollectionPanel(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';
  334. }
  335. });
  336. //==============================================================================
  337. return {
  338. CollectionPanel : CollectionPanel,
  339. ListCollectionPanel : ListCollectionPanel,
  340. PairCollectionPanel : PairCollectionPanel,
  341. ListOfPairsCollectionPanel : ListOfPairsCollectionPanel
  342. };
  343. });