PageRenderTime 55ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

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

https://bitbucket.org/dan/galaxy-central
JavaScript | 337 lines | 217 code | 32 blank | 88 comment | 28 complexity | a6ee203fb70b504a26f22e7fa5339b63 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. //logger : console,
  24. /** since history content is a mix, override model fn into a factory, creating based on history_content_type */
  25. model : function( attrs, options ) {
  26. //console.debug( 'HistoryContents.model:', 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. //TODO: could probably use the contents.history_id instead
  51. this.historyId = options.historyId;
  52. //this._setUpListeners();
  53. this.on( 'all', function(){
  54. this.debug( this + '.event:', arguments );
  55. });
  56. },
  57. /** root api url */
  58. urlRoot : galaxy_config.root + 'api/histories',
  59. /** complete api url */
  60. url : function(){
  61. return this.urlRoot + '/' + this.historyId + '/contents';
  62. },
  63. // ........................................................................ common queries
  64. /** Get the ids of every item in this collection
  65. * @returns array of encoded ids
  66. */
  67. ids : function(){
  68. //TODO: is this still useful since type_id
  69. return this.map( function( item ){ return item.get('id'); });
  70. },
  71. /** Get contents that are not ready
  72. * @returns array of content models
  73. */
  74. notReady : function(){
  75. return this.filter( function( content ){
  76. return !content.inReadyState();
  77. });
  78. },
  79. /** Get the id of every model in this collection not in a 'ready' state (running).
  80. * @returns an array of model ids
  81. * @see HistoryDatasetAssociation#inReadyState
  82. */
  83. running : function(){
  84. var idList = [];
  85. this.each( function( item ){
  86. var isRunning = !item.inReadyState();
  87. if( isRunning ){
  88. //TODO: is this still correct since type_id
  89. idList.push( item.get( 'id' ) );
  90. }
  91. });
  92. return idList;
  93. },
  94. /** Get the model with the given hid
  95. * @param {Int} hid the hid to search for
  96. * @returns {HistoryDatasetAssociation} the model with the given hid or undefined if not found
  97. */
  98. getByHid : function( hid ){
  99. return _.first( this.filter( function( content ){ return content.get( 'hid' ) === hid; }) );
  100. },
  101. //TODO:?? this may belong in the containing view
  102. /** Get every 'shown' model in this collection based on show_deleted/hidden
  103. * @param {Boolean} show_deleted are we showing deleted content?
  104. * @param {Boolean} show_hidden are we showing hidden content?
  105. * @returns array of content models
  106. * @see HistoryDatasetAssociation#isVisible
  107. */
  108. getVisible : function( show_deleted, show_hidden, filters ){
  109. filters = filters || [];
  110. //this.debug( 'filters:', filters );
  111. // always filter by show deleted/hidden first
  112. this.debug( 'checking isVisible' );
  113. var filteredHdas = new HistoryContents( this.filter( function( item ){
  114. return item.isVisible( show_deleted, show_hidden );
  115. }));
  116. _.each( filters, function( filter_fn ){
  117. if( !_.isFunction( filter_fn ) ){ return; }
  118. filteredHdas = new HistoryContents( filteredHdas.filter( filter_fn ) );
  119. });
  120. return filteredHdas;
  121. },
  122. /** return true if any contents don't have details */
  123. haveDetails : function(){
  124. return this.all( function( content ){ return content.hasDetails(); });
  125. },
  126. // ........................................................................ ajax
  127. /** fetch detailed model data for all contents in this collection */
  128. fetchAllDetails : function( options ){
  129. options = options || {};
  130. var detailsFlag = { details: 'all' };
  131. options.data = ( options.data )?( _.extend( options.data, detailsFlag ) ):( detailsFlag );
  132. return this.fetch( options );
  133. },
  134. /** using a queue, perform ajaxFn on each of the models in this collection */
  135. ajaxQueue : function( ajaxFn, options ){
  136. var deferred = jQuery.Deferred(),
  137. startingLength = this.length,
  138. responses = [];
  139. if( !startingLength ){
  140. deferred.resolve([]);
  141. return deferred;
  142. }
  143. // use reverse order (stylistic choice)
  144. var ajaxFns = this.chain().reverse().map( function( content, i ){
  145. return function(){
  146. var xhr = ajaxFn.call( content, options );
  147. // if successful, notify using the deferred to allow tracking progress
  148. xhr.done( function( response ){
  149. deferred.notify({ curr: i, total: startingLength, response: response, model: content });
  150. });
  151. // (regardless of previous error or success) if not last ajax call, shift and call the next
  152. // if last fn, resolve deferred
  153. xhr.always( function( response ){
  154. responses.push( response );
  155. if( ajaxFns.length ){
  156. ajaxFns.shift()();
  157. } else {
  158. deferred.resolve( responses );
  159. }
  160. });
  161. };
  162. }).value();
  163. // start the queue
  164. ajaxFns.shift()();
  165. return deferred;
  166. },
  167. /** copy an existing, accessible hda into this collection */
  168. copy : function( id ){
  169. //TODO: incorp collections
  170. var collection = this,
  171. xhr = jQuery.post( this.url(), {
  172. source : 'hda',
  173. content : id
  174. });
  175. xhr.done( function( json ){
  176. collection.add([ json ]);
  177. });
  178. xhr.fail( function( error, status, message ){
  179. //TODO: better distinction btwn not-allowed and actual ajax error
  180. collection.trigger( 'error', collection, xhr, {}, 'Error copying dataset' );
  181. });
  182. return xhr;
  183. },
  184. // ........................................................................ sorting/filtering
  185. /** return a new collection of contents whose attributes contain the substring matchesWhat */
  186. matches : function( matchesWhat ){
  187. return this.filter( function( content ){
  188. return content.matches( matchesWhat );
  189. });
  190. },
  191. // ........................................................................ misc
  192. /** override to get a correct/smarter merge when incoming data is partial */
  193. set : function( models, options ){
  194. this.debug( 'set:', models );
  195. // arrrrrrrrrrrrrrrrrg...
  196. // (e.g. stupid backbone)
  197. // w/o this partial models from the server will fill in missing data with model defaults
  198. // and overwrite existing data on the client
  199. // see Backbone.Collection.set and _prepareModel
  200. var collection = this;
  201. models = _.map( models, function( model ){
  202. var attrs = model.attributes || model,
  203. typeId = HISTORY_CONTENT.typeIdStr( attrs.history_content_type, attrs.id ),
  204. existing = collection.get( typeId );
  205. if( !existing ){ return model; }
  206. // merge the models _BEFORE_ calling the superclass version
  207. var merged = _.clone( existing.attributes );
  208. _.extend( merged, model );
  209. return merged;
  210. });
  211. // now call superclass when the data is filled
  212. Backbone.Collection.prototype.set.call( this, models, options );
  213. },
  214. /** Convert this ad-hoc collection of hdas to a formal collection tracked
  215. by the server.
  216. **/
  217. promoteToHistoryDatasetCollection : function _promote( history, collection_type, options ){
  218. //TODO: seems like this would be better in mvc/collections
  219. options = options || {};
  220. options.url = this.url();
  221. options.type = "POST";
  222. var full_collection_type = collection_type;
  223. var element_identifiers = [],
  224. name = null;
  225. // This mechanism is rough - no error handling, allows invalid selections, no way
  226. // for user to pick/override element identifiers. This is only really meant
  227. if( collection_type === "list" ) {
  228. this.chain().each( function( hda ) {
  229. // TODO: Handle duplicate names.
  230. var name = hda.attributes.name;
  231. var id = hda.get('id');
  232. var content_type = hda.attributes.history_content_type;
  233. if( content_type === "dataset" ) {
  234. if( full_collection_type !== "list" ) {
  235. this.log( "Invalid collection type" );
  236. }
  237. element_identifiers.push( { name: name, src: "hda", id: id } );
  238. } else {
  239. if( full_collection_type === "list" ) {
  240. full_collection_type = "list:" + hda.attributes.collection_type;
  241. } else {
  242. if( full_collection_type !== "list:" + hda.attributes.collection_type ) {
  243. this.log( "Invalid collection type" );
  244. }
  245. }
  246. element_identifiers.push( { name: name, src: "hdca", id: id } );
  247. }
  248. });
  249. name = "New Dataset List";
  250. } else if( collection_type === "paired" ) {
  251. var ids = this.ids();
  252. if( ids.length !== 2 ){
  253. // TODO: Do something...
  254. }
  255. element_identifiers.push( { name: "forward", src: "hda", id: ids[ 0 ] } );
  256. element_identifiers.push( { name: "reverse", src: "hda", id: ids[ 1 ] } );
  257. name = "New Dataset Pair";
  258. }
  259. options.data = {
  260. type: "dataset_collection",
  261. name: name,
  262. collection_type: full_collection_type,
  263. element_identifiers: JSON.stringify( element_identifiers )
  264. };
  265. var xhr = jQuery.ajax( options );
  266. xhr.done( function( message, status, responseObj ){
  267. history.refresh( );
  268. });
  269. xhr.fail( function( xhr, status, message ){
  270. if( xhr.responseJSON && xhr.responseJSON.error ){
  271. error = xhr.responseJSON.error;
  272. } else {
  273. error = xhr.responseJSON;
  274. }
  275. xhr.responseText = error;
  276. // Do something?
  277. });
  278. return xhr;
  279. },
  280. /** In this override, copy the historyId to the clone */
  281. clone : function(){
  282. var clone = Backbone.Collection.prototype.clone.call( this );
  283. clone.historyId = this.historyId;
  284. return clone;
  285. },
  286. /** debugging */
  287. print : function(){
  288. var contents = this;
  289. contents.each( function( c ){
  290. contents.debug( c );
  291. if( c.elements ){
  292. contents.debug( '\t elements:', c.elements );
  293. }
  294. });
  295. },
  296. /** String representation. */
  297. toString : function(){
  298. return ([ 'HistoryContents(', [ this.historyId, this.length ].join(), ')' ].join( '' ));
  299. }
  300. });
  301. //==============================================================================
  302. return {
  303. HistoryContents : HistoryContents
  304. };
  305. });