PageRenderTime 125ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/static/scripts/mvc/history/history-contents.js

https://bitbucket.org/kellrott/galaxy-central
JavaScript | 310 lines | 197 code | 30 blank | 83 comment | 28 complexity | 2f7b538f7a78bfd771b793dd68b95390 MD5 | raw file
Possible License(s): CC-BY-3.0
  1. define([
  2. "mvc/history/history-content-model",
  3. "mvc/history/hda-model",
  4. "mvc/history/hdca-model",
  5. "mvc/base-mvc",
  6. "utils/localization"
  7. ], function( HISTORY_CONTENT, HDA_MODEL, HDCA_MODEL, BASE_MVC, _l ){
  8. //==============================================================================
  9. /** @class Backbone collection for history content.
  10. * NOTE: history content seems like a dataset collection, but differs in that it is mixed:
  11. * each element can be either an HDA (dataset) or a DatasetCollection and co-exist on
  12. * the same level.
  13. * Dataset collections on the other hand are not mixed and (so far) can only contain either
  14. * HDAs or child dataset collections on one level.
  15. * This is why this does not inherit from any of the DatasetCollections (currently).
  16. */
  17. var HistoryContents = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend(
  18. /** @lends HistoryContents.prototype */{
  19. //TODO:?? may want to inherit from some MixedModelCollection
  20. //TODO:?? also consider inheriting from a 'DatasetList'
  21. //TODO: can we decorate the mixed models using the model fn below (instead of having them build their own type_id)?
  22. /** logger used to record this.log messages, commonly set to console */
  23. // comment this out to suppress log output
  24. //logger : console,
  25. /** since history content is a mix, override model fn into a factory, creating based on history_content_type */
  26. model : function( attrs, options ) {
  27. //TODO: can we move the type_id stuff here?
  28. //attrs.type_id = typeIdStr( attrs );
  29. if( attrs.history_content_type === "dataset" ) {
  30. return new HDA_MODEL.HistoryDatasetAssociation( attrs, options );
  31. } else if( attrs.history_content_type === "dataset_collection" ) {
  32. switch( attrs.collection_type ){
  33. case 'list':
  34. return new HDCA_MODEL.HistoryListDatasetCollection( attrs, options );
  35. case 'paired':
  36. return new HDCA_MODEL.HistoryPairDatasetCollection( attrs, options );
  37. case 'list:paired':
  38. return new HDCA_MODEL.HistoryListPairedDatasetCollection( attrs, options );
  39. }
  40. throw new TypeError( 'Unknown collection_type: ' + attrs.collection_type );
  41. }
  42. // TODO: Handle unknown history_content_type...
  43. throw new TypeError( 'Unknown history_content_type: ' + attrs.history_content_type );
  44. },
  45. /** Set up.
  46. * @see Backbone.Collection#initialize
  47. */
  48. initialize : function( models, options ){
  49. options = options || {};
  50. this.historyId = options.historyId;
  51. //this._setUpListeners();
  52. this.on( 'all', function(){
  53. this.debug( this + '.event:', arguments );
  54. });
  55. },
  56. /** root api url */
  57. urlRoot : galaxy_config.root + 'api/histories',
  58. /** complete api url */
  59. url : function(){
  60. return this.urlRoot + '/' + this.historyId + '/contents';
  61. },
  62. // ........................................................................ common queries
  63. /** Get the ids of every item in this collection
  64. * @returns array of encoded ids
  65. */
  66. ids : function(){
  67. //TODO: is this still useful since type_id
  68. return this.map( function( item ){ return item.get('id'); });
  69. },
  70. /** Get contents that are not ready
  71. * @returns array of content models
  72. */
  73. notReady : function(){
  74. return this.filter( function( content ){
  75. return !content.inReadyState();
  76. });
  77. },
  78. /** Get the id of every model in this collection not in a 'ready' state (running).
  79. * @returns an array of model ids
  80. * @see HistoryDatasetAssociation#inReadyState
  81. */
  82. running : function(){
  83. var idList = [];
  84. this.each( function( item ){
  85. var isRunning = !item.inReadyState();
  86. if( isRunning ){
  87. //TODO: is this still correct since type_id
  88. idList.push( item.get( 'id' ) );
  89. }
  90. });
  91. return idList;
  92. },
  93. /** Get the model with the given hid
  94. * @param {Int} hid the hid to search for
  95. * @returns {HistoryDatasetAssociation} the model with the given hid or undefined if not found
  96. */
  97. getByHid : function( hid ){
  98. return _.first( this.filter( function( content ){ return content.get( 'hid' ) === hid; }) );
  99. },
  100. //TODO:?? this may belong in the containing view
  101. /** Get every 'shown' model in this collection based on show_deleted/hidden
  102. * @param {Boolean} show_deleted are we showing deleted content?
  103. * @param {Boolean} show_hidden are we showing hidden content?
  104. * @returns array of content models
  105. * @see HistoryDatasetAssociation#isVisible
  106. */
  107. getVisible : function( show_deleted, show_hidden, filters ){
  108. filters = filters || [];
  109. //this.debug( 'filters:', filters );
  110. // always filter by show deleted/hidden first
  111. this.debug( 'checking isVisible' );
  112. var filteredHdas = new HistoryContents( this.filter( function( item ){
  113. return item.isVisible( show_deleted, show_hidden );
  114. }));
  115. _.each( filters, function( filter_fn ){
  116. if( !_.isFunction( filter_fn ) ){ return; }
  117. filteredHdas = new HistoryContents( filteredHdas.filter( filter_fn ) );
  118. });
  119. return filteredHdas;
  120. },
  121. /** return true if any contents don't have details */
  122. haveDetails : function(){
  123. return this.all( function( content ){ return content.hasDetails(); });
  124. },
  125. // ........................................................................ ajax
  126. /** fetch detailed model data for all contents in this collection */
  127. fetchAllDetails : function( options ){
  128. options = options || {};
  129. var detailsFlag = { details: 'all' };
  130. options.data = ( options.data )?( _.extend( options.data, detailsFlag ) ):( detailsFlag );
  131. return this.fetch( options );
  132. },
  133. /** using a queue, perform ajaxFn on each of the models in this collection */
  134. ajaxQueue : function( ajaxFn, options ){
  135. var deferred = jQuery.Deferred(),
  136. startingLength = this.length,
  137. responses = [];
  138. if( !startingLength ){
  139. deferred.resolve([]);
  140. return deferred;
  141. }
  142. // use reverse order (stylistic choice)
  143. var ajaxFns = this.chain().reverse().map( function( content, i ){
  144. return function(){
  145. var xhr = ajaxFn.call( content, options );
  146. // if successful, notify using the deferred to allow tracking progress
  147. xhr.done( function( response ){
  148. deferred.notify({ curr: i, total: startingLength, response: response, model: content });
  149. });
  150. // (regardless of previous error or success) if not last ajax call, shift and call the next
  151. // if last fn, resolve deferred
  152. xhr.always( function( response ){
  153. responses.push( response );
  154. if( ajaxFns.length ){
  155. ajaxFns.shift()();
  156. } else {
  157. deferred.resolve( responses );
  158. }
  159. });
  160. };
  161. }).value();
  162. // start the queue
  163. ajaxFns.shift()();
  164. return deferred;
  165. },
  166. // ........................................................................ sorting/filtering
  167. /** return a new collection of contents whose attributes contain the substring matchesWhat */
  168. matches : function( matchesWhat ){
  169. return this.filter( function( content ){
  170. return content.matches( matchesWhat );
  171. });
  172. },
  173. // ........................................................................ misc
  174. /** override to get a correct/smarter merge when incoming data is partial */
  175. set : function( models, options ){
  176. // arrrrrrrrrrrrrrrrrg...
  177. // (e.g. stupid backbone)
  178. // w/o this partial models from the server will fill in missing data with model defaults
  179. // and overwrite existing data on the client
  180. // see Backbone.Collection.set and _prepareModel
  181. var collection = this;
  182. models = _.map( models, function( model ){
  183. var attrs = model.attributes || model,
  184. typeId = HISTORY_CONTENT.typeIdStr( attrs.history_content_type, attrs.id ),
  185. existing = collection.get( typeId );
  186. if( !existing ){ return model; }
  187. // merge the models _BEFORE_ calling the superclass version
  188. var merged = existing.toJSON();
  189. _.extend( merged, model );
  190. return merged;
  191. });
  192. // now call superclass when the data is filled
  193. Backbone.Collection.prototype.set.call( this, models, options );
  194. },
  195. /** Convert this ad-hoc collection of hdas to a formal collection tracked
  196. by the server.
  197. **/
  198. promoteToHistoryDatasetCollection : function _promote( history, collection_type, options ){
  199. //TODO: seems like this would be better in mvc/collections
  200. options = options || {};
  201. options.url = this.url();
  202. options.type = "POST";
  203. var full_collection_type = collection_type;
  204. var element_identifiers = [],
  205. name = null;
  206. // This mechanism is rough - no error handling, allows invalid selections, no way
  207. // for user to pick/override element identifiers. This is only really meant
  208. if( collection_type === "list" ) {
  209. this.chain().each( function( hda ) {
  210. // TODO: Handle duplicate names.
  211. var name = hda.attributes.name;
  212. var id = hda.get('id');
  213. var content_type = hda.attributes.history_content_type;
  214. if( content_type === "dataset" ) {
  215. if( full_collection_type !== "list" ) {
  216. this.log( "Invalid collection type" );
  217. }
  218. element_identifiers.push( { name: name, src: "hda", id: id } );
  219. } else {
  220. if( full_collection_type === "list" ) {
  221. full_collection_type = "list:" + hda.attributes.collection_type;
  222. } else {
  223. if( full_collection_type !== "list:" + hda.attributes.collection_type ) {
  224. this.log( "Invalid collection type" );
  225. }
  226. }
  227. element_identifiers.push( { name: name, src: "hdca", id: id } );
  228. }
  229. });
  230. name = "New Dataset List";
  231. } else if( collection_type === "paired" ) {
  232. var ids = this.ids();
  233. if( ids.length !== 2 ){
  234. // TODO: Do something...
  235. }
  236. element_identifiers.push( { name: "forward", src: "hda", id: ids[ 0 ] } );
  237. element_identifiers.push( { name: "reverse", src: "hda", id: ids[ 1 ] } );
  238. name = "New Dataset Pair";
  239. }
  240. options.data = {
  241. type: "dataset_collection",
  242. name: name,
  243. collection_type: full_collection_type,
  244. element_identifiers: JSON.stringify( element_identifiers )
  245. };
  246. var xhr = jQuery.ajax( options );
  247. xhr.done( function( message, status, responseObj ){
  248. history.refresh( );
  249. });
  250. xhr.fail( function( xhr, status, message ){
  251. if( xhr.responseJSON && xhr.responseJSON.error ){
  252. error = xhr.responseJSON.error;
  253. } else {
  254. error = xhr.responseJSON;
  255. }
  256. xhr.responseText = error;
  257. // Do something?
  258. });
  259. return xhr;
  260. },
  261. /** debugging */
  262. print : function(){
  263. var contents = this;
  264. contents.each( function( c ){
  265. contents.debug( c );
  266. if( c.elements ){
  267. contents.debug( '\t elements:', c.elements );
  268. }
  269. });
  270. },
  271. /** String representation. */
  272. toString : function(){
  273. return ([ 'HistoryContents(', [ this.historyId, this.length ].join(), ')' ].join( '' ));
  274. }
  275. });
  276. //==============================================================================
  277. return {
  278. HistoryContents : HistoryContents
  279. };
  280. });