PageRenderTime 49ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/static/scripts/mvc/dataset/dataset-choice.js

https://bitbucket.org/remy_d1/galaxy-central-manageapi
JavaScript | 442 lines | 245 code | 42 blank | 155 comment | 21 complexity | d95085aebbf9aa737238d421120aa494 MD5 | raw file
Possible License(s): CC-BY-3.0
  1. define([
  2. 'mvc/dataset/dataset-model',
  3. 'mvc/dataset/dataset-list',
  4. 'mvc/ui/ui-modal',
  5. 'mvc/base-mvc',
  6. 'utils/localization'
  7. ], function( DATASET, DATASET_LIST, MODAL, BASE_MVC, _l ){
  8. /* ============================================================================
  9. TODO:
  10. does this really work with mixed contents?
  11. Single dataset choice: allow none?
  12. tooltips rendered *behind* modal
  13. collection selector
  14. better handling when no results returned from filterDatasetJSON
  15. pass optional subtitle from choice display to modal
  16. onfirstclick
  17. drop target
  18. return modal with promise?
  19. auto showing the modal may not be best
  20. add hidden inputs
  21. // cut1 on single dataset (17 in the list)
  22. __switch_default__ select_single
  23. input 17
  24. // cut1 on two datasets
  25. __switch_default__ select_single
  26. input|__multirun__ 13
  27. input|__multirun__ 15
  28. // cut1 on a collection
  29. __switch_default__ select_collection
  30. input|__collection_multirun__ f2db41e1fa331b3e
  31. ============================================================================ */
  32. /** Filters an array of dataset plain JSON objs.
  33. */
  34. function _filterDatasetJSON( datasetJSON, where, datasetsOnly ){
  35. //TODO: replace with _.matches (underscore 1.6.0)
  36. function matches( obj, toMatch ){
  37. for( var key in toMatch ){
  38. if( toMatch.hasOwnProperty( key ) ){
  39. if( obj[ key ] !== toMatch[ key ] ){
  40. return false;
  41. }
  42. }
  43. }
  44. return true;
  45. }
  46. return datasetJSON.filter( function( json ){
  47. return ( !json.deleted && json.visible )
  48. && ( !datasetsOnly || json.collection_type === undefined )
  49. && ( matches( json, where ) );
  50. });
  51. }
  52. // ============================================================================
  53. /** Given an array of plain JSON objs rep. datasets, show a modal allowing a choice
  54. * of one or more of those datasets.
  55. *
  56. * Pass:
  57. * an array of plain JSON objects representing allowed dataset choices
  58. * a map of options (see below)
  59. *
  60. * Options:
  61. * datasetsOnly: T: display only datasets, F: datasets + dataset collections
  62. * where: a map of attributes available choices *must have* (defaults to { state: 'ok' })
  63. * multiselect: T: user can select more than one, F: only one
  64. * selected: array of dataset ids to make them selected by default
  65. *
  66. * @example:
  67. * var datasetJSON = // from ajax or bootstrap
  68. * // returns a jQuery promise (that 'fail's only if no datasets are found matching 'where' below)
  69. * var choice = new DatasetChoiceModal( datasetJSON, {
  70. * datasetsOnly : false,
  71. * where : { state: 'ok', file_ext: 'bed', ... },
  72. * multiselect : true,
  73. * selected : [ 'df7a1f0c02a5b08e', 'abcdef0123456789' ]
  74. *
  75. * }).done( function( json ){
  76. * if( json ){
  77. * console.debug( json );
  78. * // returned choice will always be an array (single or multi)
  79. * // [{ <selected dataset JSON 1>, <selected dataset JSON 2>, ... }]
  80. * // ... do stuff
  81. * } else {
  82. * // json will === null if the user cancelled selection
  83. * console.debug( 'cancelled' );
  84. * }
  85. * });
  86. */
  87. var DatasetChoiceModal = function( datasetJSON, options ){
  88. // option defaults
  89. options = _.defaults( options || {}, {
  90. // show datasets or datasets and collections
  91. datasetsOnly : true,
  92. // map of attributes to filter datasetJSON by
  93. where : { state: 'ok' },
  94. // select more than one dataset?
  95. multiselect : false,
  96. // any dataset ids that will display as already selected
  97. selected : []
  98. });
  99. // default title should depend on multiselect
  100. options.title = options.title ||
  101. ( options.multiselect? _l( 'Choose datasets:' ): _l( 'Choose a dataset:' ) );
  102. var modal, list, buttons,
  103. promise = jQuery.Deferred(),
  104. filterFn = options.filter || _filterDatasetJSON;
  105. // filter the given datasets and if none left return a rejected promise for use with fail()
  106. datasetJSON = filterFn( datasetJSON, options.where, options.datasetsOnly );
  107. if( !datasetJSON.length ){
  108. return promise.reject( 'No matches found' );
  109. }
  110. // resolve the returned promise with the json of the selected datasets
  111. function resolveWithSelected(){
  112. promise.resolve( list.getSelectedModels().map( function( model ){
  113. return model.toJSON();
  114. }));
  115. }
  116. // if multiselect - add a button for the user to complete the changes
  117. if( options.multiselect ){
  118. buttons = {};
  119. buttons[ _l( 'Ok' ) ] = resolveWithSelected;
  120. }
  121. // create a full-height modal that's cancellable, remove unneeded elements and styles
  122. modal = new MODAL.View({
  123. height : 'auto',
  124. buttons : buttons,
  125. closing_events : true,
  126. closing_callback : function(){ promise.resolve( null ); },
  127. body : [
  128. '<div class="list-panel"></div>'
  129. ].join('')
  130. });
  131. modal.$( '.modal-header' ).remove();
  132. modal.$( '.modal-footer' ).css( 'margin-top', '0px' );
  133. // attach a dataset list (of the filtered datasets) to that modal that's selectable
  134. list = new DATASET_LIST.DatasetList({
  135. title : options.title,
  136. subtitle : options.subtitle || _l([
  137. //TODO: as option
  138. 'Click the checkboxes on the right to select datasets. ',
  139. 'Click the datasets names to see their details. '
  140. ].join('')),
  141. el : modal.$body.find( '.list-panel' ),
  142. selecting : true,
  143. selected : options.selected,
  144. collection : new DATASET.DatasetAssociationCollection( datasetJSON )
  145. });
  146. // when the list is rendered, show the modal (also add a specifying class for css)
  147. list.once( 'rendered:initial', function(){
  148. modal.show();
  149. modal.$el.addClass( 'dataset-choice-modal' );
  150. });
  151. if( !options.multiselect ){
  152. // if single select, remove the all/none list actions from the panel
  153. list.on( 'rendered', function(){
  154. list.$( '.list-actions' ).hide();
  155. });
  156. // if single select, immediately resolve on a single selection
  157. list.on( 'view:selected', function( view ){
  158. promise.resolve([ view.model.toJSON() ]);
  159. });
  160. }
  161. list.render( 0 );
  162. // return the promise, and on any resolution close the modal
  163. return promise.always( function(){
  164. modal.hide();
  165. });
  166. };
  167. // ============================================================================
  168. /** Activator for single dataset selection modal and display of the selected dataset.
  169. * The activator/display will show as a single div and, when a dataset is selected,
  170. * show the name and details of the selected dataset.
  171. *
  172. * When clicked the div will generate a DatasetChoiceModal of the available choices.
  173. *
  174. * Options:
  175. * datasetJSON: array of plain json objects representing allowed choices
  176. * datasetsOnly: T: only show datasets in the allowed choices, F: datasets + collections
  177. * where: map of attributes to filter datasetJSON by (e.g. { file_ext: 'bed' })
  178. * label: the label/prompt displayed
  179. * selected: array of dataset ids that will show as already selected in the control
  180. *
  181. * @example:
  182. * var choice1 = new DATASET_CHOICE.DatasetChoice({
  183. * datasetJSON : datasetJSON,
  184. * label : 'Input dataset',
  185. * selected : [ 'df7a1f0c02a5b08e' ]
  186. * });
  187. * $( 'body' ).append( choice1.render().$el )
  188. *
  189. * Listen to the DatasetChoice to react to changes in the user's choice/selection:
  190. * @example:
  191. * choice1.on( 'selected', function( chooser, selectionJSONArray ){
  192. * // ... do stuff with new selections
  193. * });
  194. */
  195. var DatasetChoice = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({
  196. //logger : console,
  197. className : 'dataset-choice',
  198. /** set up defaults, options, and listeners */
  199. initialize : function( attributes ){
  200. this.debug( this + '(DatasetChoice).initialize:', attributes );
  201. this.label = attributes.label !== undefined? _l( attributes.label ) : '';
  202. this.where = attributes.where;
  203. this.datasetsOnly = attributes.datasetsOnly !== undefined? attributes.datasetsOnly: true;
  204. this.datasetJSON = attributes.datasetJSON || [];
  205. this.selected = attributes.selected || [];
  206. this._setUpListeners();
  207. },
  208. /** add any (bbone) listeners */
  209. _setUpListeners : function(){
  210. //this.on( 'all', function(){
  211. // this.log( this + '', arguments );
  212. //});
  213. },
  214. /** render the view */
  215. render : function(){
  216. var json = this.toJSON();
  217. this.$el.html( this._template( json ) );
  218. this.$( '.selected' ).replaceWith( this._renderSelected( json ) );
  219. return this;
  220. },
  221. /** return plain html for the overall control */
  222. _template : function( json ){
  223. return _.template([
  224. '<label>',
  225. '<span class="prompt"><%= json.label %></span>',
  226. '<div class="selected"></div>',
  227. '</label>'
  228. ].join(''), { json: json });
  229. },
  230. /** return jQ DOM for the selected dataset (only one) */
  231. _renderSelected : function( json ){
  232. if( json.selected.length ){
  233. //TODO: break out?
  234. return $( _.template([
  235. '<div class="selected">',
  236. '<span class="title"><%= selected.hid %>: <%= selected.name %></span>',
  237. '<span class="subtitle">',
  238. '<i><%= selected.misc_blurb %></i>',
  239. '<i>', _l( 'format' ) + ': ', '<%= selected.file_ext %></i>',
  240. '<i><%= selected.misc_info %></i>',
  241. '</span>',
  242. '</div>'
  243. ].join( '' ), { selected: json.selected[0] }));
  244. }
  245. return $([
  246. '<span class="none-selected-msg">(',
  247. _l( 'click to select a dataset' ),
  248. ')</span>'
  249. ].join( '' ));
  250. },
  251. //TODO:?? why not just pass in view?
  252. /** return a plain JSON object with both the view and dataset attributes */
  253. toJSON : function(){
  254. var chooser = this;
  255. return {
  256. label : chooser.label,
  257. datasets : chooser.datasetJSON,
  258. selected : _.compact( _.map( chooser.selected, function( id ){
  259. return _.findWhere( chooser.datasetJSON, { id: id });
  260. }))
  261. };
  262. },
  263. /** event map: when to open the modal */
  264. events : {
  265. // the whole thing functions as a button
  266. 'click' : 'chooseWithModal'
  267. },
  268. //TODO:?? modal to prop of this?
  269. //TODO:?? should be able to handle 'none selectable' on initialize
  270. /** open the modal and handle the promise representing the user's choice
  271. * @fires 'selected' when the user selects dataset(s) - passed full json of the selected datasets
  272. * @fires 'cancelled' when the user clicks away/closes the modal (no selection made) - passed this
  273. * @fires 'error' if the modal has no selectable datasets based on this.where - passed this and other args
  274. */
  275. chooseWithModal : function(){
  276. var chooser = this;
  277. return this._createModal()
  278. .done( function( json ){
  279. if( json ){
  280. chooser.selected = _.pluck( json, 'id' );
  281. chooser.trigger( 'selected', chooser, json );
  282. chooser.render();
  283. } else {
  284. chooser.trigger( 'cancelled', chooser );
  285. }
  286. })
  287. .fail( function(){
  288. chooser.trigger( 'error', chooser, arguments );
  289. });
  290. },
  291. /** create and return the modal to use for choosing */
  292. _createModal : function(){
  293. return new DatasetChoiceModal( this.datasetJSON, this._getModalOptions() );
  294. },
  295. /** return a plain JSON containing the options to pass to the modal */
  296. _getModalOptions : function(){
  297. return {
  298. title : this.label,
  299. multiselect : false,
  300. selected : this.selected,
  301. where : this.where,
  302. datasetsOnly : this.datasetsOnly
  303. };
  304. },
  305. // ------------------------------------------------------------------------ misc
  306. /** string rep */
  307. toString : function(){
  308. return 'DatasetChoice(' + this.selected + ')';
  309. }
  310. });
  311. // ============================================================================
  312. /** Activator for multiple dataset selection modal and display of the selected datasets.
  313. * The activator/display will show as a table of all choices.
  314. *
  315. * See DatasetChoice (above) for example usage.
  316. *
  317. * Additional options:
  318. * showHeaders: T: show headers for selected dataset attributes in the display table
  319. * cells: map of attribute keys -> Human readable/localized column headers
  320. * (e.g. { file_ext: _l( 'Format' ) }) - defaults are listed below
  321. */
  322. var MultiDatasetChoice = DatasetChoice.extend({
  323. className : DatasetChoice.prototype.className + ' multi',
  324. /** default (dataset attribute key -> table header text) map of what cells to display in the table */
  325. cells : {
  326. hid : _l( 'History #' ),
  327. name : _l( 'Name' ),
  328. misc_blurb : _l( 'Summary' ),
  329. file_ext : _l( 'Format' ),
  330. genome_build : _l( 'Genome' ),
  331. tags : _l( 'Tags' ),
  332. annotation : _l( 'Annotation' )
  333. },
  334. /** in this override, add the showHeaders and cells options */
  335. initialize : function( attributes ){
  336. this.showHeaders = attributes.showHeaders !== undefined? attributes.showHeaders : true;
  337. this.cells = attributes.cells || this.cells;
  338. DatasetChoice.prototype.initialize.call( this, attributes );
  339. },
  340. /** in this override, display the selected datasets as a table with optional headers */
  341. _renderSelected : function( json ){
  342. if( json.selected.length ){
  343. return $( _.template([
  344. '<table class="selected">',
  345. '<% if( json.showHeaders ){ %>',
  346. '<thead><tr>',
  347. '<% _.map( json.cells, function( val, key ){ %>',
  348. '<th><%= val %></th>',
  349. '<% }); %>',
  350. '</tr></thead>',
  351. '<% } %>',
  352. '<tbody>',
  353. '<% _.map( json.selected, function( selected ){ %>',
  354. '<tr>',
  355. '<% _.map( json.cells, function( val, key ){ %>',
  356. '<td class="cell-<%= key %>"><%= selected[ key ] %></td>',
  357. '<% }) %>',
  358. '</tr>',
  359. '<% }); %>',
  360. '</tbody>',
  361. '</table>'
  362. ].join( '' ), { json: json }));
  363. }
  364. return $([
  365. '<span class="none-selected-msg">(',
  366. _l( 'click to select a dataset' ),
  367. ')</span>'
  368. ].join( '' ));
  369. },
  370. /** in this override, send the showHeaders and cells options as well */
  371. toJSON : function(){
  372. return _.extend( DatasetChoice.prototype.toJSON.call( this ), {
  373. showHeaders : this.showHeaders,
  374. cells : this.cells
  375. });
  376. },
  377. /** in this override, set multiselect to true */
  378. _getModalOptions : function(){
  379. return _.extend( DatasetChoice.prototype._getModalOptions.call( this ), {
  380. multiselect : true
  381. });
  382. },
  383. // ------------------------------------------------------------------------ misc
  384. /** string rep */
  385. toString : function(){
  386. return 'DatasetChoice(' + this.selected + ')';
  387. }
  388. });
  389. // ============================================================================
  390. return {
  391. DatasetChoiceModal : DatasetChoiceModal,
  392. DatasetChoice : DatasetChoice,
  393. MultiDatasetChoice : MultiDatasetChoice
  394. };
  395. });