diff --git a/client/galaxy/scripts/mvc/tool/tool-form-base.js b/client/galaxy/scripts/mvc/tool/tool-form-base.js index 6b577b0a7a6..c84d77502df 100644 --- a/client/galaxy/scripts/mvc/tool/tool-form-base.js +++ b/client/galaxy/scripts/mvc/tool/tool-form-base.js @@ -16,12 +16,14 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-misc', 'mvc/form/form-view', self._buildModel(process, options, true); }); } - // Listen to history panel + // listen to history panel if ( options.listen_to_history && parent.Galaxy && parent.Galaxy.currHistoryPanel ) { this.listenTo( parent.Galaxy.currHistoryPanel.collection, 'change', function() { this.refresh(); }); } + // destroy dom elements + this.$el.on( 'remove', function() { self.remove() } ); }, /** Listen to history panel changes and update the tool form */ @@ -101,9 +103,9 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-misc', 'mvc/form/form-view', Galaxy.emit.debug('tool-form-base::initialize()', 'Initial tool model ready.', new_model); process.resolve(); }, - error : function(response, xhr) { + error : function(response, status) { var error_message = ( response && response.err_msg ) || 'Uncaught error.'; - if ( xhr.status == 401 ) { + if ( status == 401 ) { window.location = Galaxy.root + 'user/login?' + $.param({ redirect : Galaxy.root + '?tool_id=' + self.options.id }); } else if ( self.$el.is(':empty') ) { self.$el.prepend((new Ui.Message({ diff --git a/client/galaxy/scripts/mvc/tool/tool-form-composite.js b/client/galaxy/scripts/mvc/tool/tool-form-composite.js index 7dc1cf2ccb2..e5049d1fbbf 100644 --- a/client/galaxy/scripts/mvc/tool/tool-form-composite.js +++ b/client/galaxy/scripts/mvc/tool/tool-form-composite.js @@ -196,6 +196,15 @@ define([ 'utils/utils', 'utils/deferred', 'mvc/ui/ui-misc', 'mvc/form/form-view' fade : false } ).$el ); } + var step_version_changes = this.model.get( 'step_version_changes' ); + if ( step_version_changes && step_version_changes.length > 0 ) { + this.$message.append( new Ui.Message( { + message : 'Some tools are being executed with different versions compared to those available when this workflow was last saved because the other versions are not or no longer available on this Galaxy instance. To upgrade your workflow and dismiss this message simply edit the workflow and re-save it.', + status : 'warning', + persistent : true, + fade : false + } ).$el ); + } }, /** Render workflow parameters */ @@ -347,7 +356,7 @@ define([ 'utils/utils', 'utils/deferred', 'mvc/ui/ui-misc', 'mvc/form/form-view' _submit: function() { var self = this; var job_def = { - new_history_name : this.history_form.data.create()[ 'new_history|name' ], + new_history_name : this.history_form ? this.history_form.data.create()[ 'new_history|name' ] : {}, replacement_params : this.wp_form ? this.wp_form.data.create() : {}, inputs : {} }; @@ -430,14 +439,14 @@ define([ 'utils/utils', 'utils/deferred', 'mvc/ui/ui-misc', 'mvc/form/form-view' /** Append new dom to body */ _append: function( $container, $el ) { - $container.append( '
' ).addClass( 'ui-margin-top' ).append( $el ); + $container.append( '' ).append( $el ); }, /** Set enabled/disabled state */ _enabled: function( enabled ) { this.execute_btn.model.set( { wait: !enabled, wait_text: 'Sending...', percentage: -1 } ); this.wp_form && this.wp_form.portlet[ enabled ? 'enable' : 'disable' ](); - this.history_form.portlet[ enabled ? 'enable' : 'disable' ](); + this.history_form && this.history_form.portlet[ enabled ? 'enable' : 'disable' ](); _.each( this.forms, function( form ) { form && form.portlet[ enabled ? 'enable' : 'disable' ]() } ); }, diff --git a/client/galaxy/scripts/mvc/tool/tool-form-workflow.js b/client/galaxy/scripts/mvc/tool/tool-form-workflow.js index 8052e03dcd4..becb04ea06a 100644 --- a/client/galaxy/scripts/mvc/tool/tool-form-workflow.js +++ b/client/galaxy/scripts/mvc/tool/tool-form-workflow.js @@ -1,85 +1,59 @@ -/** - This is the workflow tool form. -*/ -define(['utils/utils', 'mvc/tool/tool-form-base'], - function(Utils, ToolFormBase) { - - // create form view - var View = ToolFormBase.extend({ - initialize: function(options) { +/** This is the workflow tool form. */ +define( [ 'utils/utils', 'mvc/tool/tool-form-base' ], + function( Utils, ToolFormBase ) { + var View = Backbone.View.extend({ + initialize: function( options ) { var self = this; this.workflow = options.workflow; - - // link with node representation in workflow module - this.node = options.node; - if (!this.node) { - Galaxy.emit.debug('tool-form-workflow::initialize()', 'Node not found in workflow.'); - return; - } - - // link actions - this.post_job_actions = this.node.post_job_actions || {}; - - // initialize parameters - options = Utils.merge(options, { - // set labels - text_enable : 'Set in Advance', - text_disable : 'Set at Runtime', - - // configure workflow style - narrow : true, - initial_errors : true, - sustain_version : true, - cls : 'ui-portlet-narrow', - - // configure model update - update_url : Galaxy.root + 'api/workflows/build_module', - update : function(data) { - // This hasn't modified the workflow, just returned - // module information for the tool to update the workflow - // state stored on the client with. User needs to save - // for this to take effect. - self.node.update_field_data(data); - self.errors(data && data.tool_model) - } - }); - - // mark values which can be determined at runtime - Utils.deepeach (options.inputs, function( input ) { - if ( input.type ) { - if ( [ 'data', 'data_collection' ].indexOf( input.type ) != -1 ) { - input.type = 'hidden'; - input.info = 'Data input \'' + input.name + '\' (' + Utils.textify( input.extensions ) + ')'; - input.value = { '__class__': 'RuntimeValue' }; - } else { - input.collapsible_value = { '__class__': 'RuntimeValue' }; - input.is_workflow = ( input.options && input.options.length == 0 ) || - ( [ 'integer', 'float' ].indexOf( input.type ) != -1 ); + this.node = options.node; + this.setElement( '' ); + if ( this.node ) { + this.post_job_actions = this.node.post_job_actions || {}; + Utils.deepeach( options.inputs, function( input ) { + if ( input.type ) { + if ( [ 'data', 'data_collection' ].indexOf( input.type ) != -1 ) { + input.type = 'hidden'; + input.info = 'Data input \'' + input.name + '\' (' + Utils.textify( input.extensions ) + ')'; + input.value = { '__class__': 'RuntimeValue' }; + } else { + input.collapsible_value = { '__class__': 'RuntimeValue' }; + input.is_workflow = ( input.options && input.options.length == 0 ) || + ( [ 'integer', 'float' ].indexOf( input.type ) != -1 ); + } } - } - }); - - // declare conditional and data input fields as not collapsible - Utils.deepeach(options.inputs, function(item) { - item.type == 'conditional' && ( item.test_param.collapsible_value = undefined ); - }); - - // configure custom sections - this._makeSections(options); - - // create final tool form - ToolFormBase.prototype.initialize.call(this, options); + }); + Utils.deepeach( options.inputs, function( input ) { + input.type == 'conditional' && ( input.test_param.collapsible_value = undefined ); + }); + this._makeSections( options ); + this.form = new ToolFormBase( Utils.merge( options, { + text_enable : 'Set in Advance', + text_disable : 'Set at Runtime', + narrow : true, + initial_errors : true, + sustain_version : true, + cls : 'ui-portlet-narrow', + update_url : Galaxy.root + 'api/workflows/build_module', + update : function( data ) { + // This hasn't modified the workflow, just returned + // module information for the tool to update the workflow + // state stored on the client with. User needs to save + // for this to take effect. + self.node.update_field_data( data ); + self.form.errors( data && data.tool_model ) + } + })); + this.$el.append( this.form.$el ); + } else { + Galaxy.emit.debug('tool-form-workflow::initialize()', 'Node not found in workflow.'); + } }, - /** Builds all sub sections - */ - _makeSections: function(options){ - // initialize local variables + /** Builds all sub sections */ + _makeSections: function( options ){ var inputs = options.inputs; var datatypes = options.datatypes; - - // for annotation - inputs[Utils.uid()] = { + inputs[ Utils.uid() ] = { label : 'Annotation / Notes', name : 'annotation', type : 'text', @@ -87,65 +61,47 @@ define(['utils/utils', 'mvc/tool/tool-form-base'], help : 'Add an annotation or note for this step. It will be shown with the workflow.', value : this.node.annotation } - - // get first output id - var output_id = this.node.output_terminals && Object.keys(this.node.output_terminals)[0]; - if (output_id) { - // send email on job completion - inputs[Utils.uid()] = { + var output_id = this.node.output_terminals && Object.keys( this.node.output_terminals )[ 0 ]; + if ( output_id ) { + inputs[ Utils.uid() ] = { name : 'pja__' + output_id + '__EmailAction', label : 'Email notification', type : 'boolean', - value : String(Boolean(this.post_job_actions['EmailAction' + output_id])), + value : String( Boolean( this.post_job_actions[ 'EmailAction' + output_id ] ) ), ignore : 'false', help : 'An email notification will be sent when the job has completed.', payload : { 'host' : window.location.host } }; - - // delete non-output files - inputs[Utils.uid()] = { + inputs[ Utils.uid() ] = { name : 'pja__' + output_id + '__DeleteIntermediatesAction', label : 'Output cleanup', type : 'boolean', - value : String(Boolean(this.post_job_actions['DeleteIntermediatesAction' + output_id])), + value : String( Boolean( this.post_job_actions[ 'DeleteIntermediatesAction' + output_id ] ) ), ignore : 'false', help : 'Delete intermediate outputs if they are not used as input for another job.' }; - - // add output specific actions - for (var i in this.node.output_terminals) { - inputs[Utils.uid()] = this._makeSection(i, datatypes); + for ( var i in this.node.output_terminals ) { + inputs[ Utils.uid() ] = this._makeSection( i, datatypes ); } } }, - /** Builds sub section with step actions/annotation - */ - _makeSection: function(output_id, datatypes){ - // format datatypes + /** Builds sub section with step actions/annotation */ + _makeSection: function( output_id, datatypes ){ var self = this; var extensions = []; var input_terminal_names = []; - - for (key in datatypes) { - extensions.push({ - 0 : datatypes[key], - 1 : datatypes[key] - }); + for ( key in datatypes ) { + extensions.push( { 0 : datatypes[ key ], 1 : datatypes[ key ] } ); } - - for (key in this.node.input_terminals){ - input_terminal_names.push(this.node.input_terminals[key].name); + for ( key in this.node.input_terminals ){ + input_terminal_names.push( this.node.input_terminals[ key ].name ); } - - // sort extensions - extensions.sort(function(a, b) { + extensions.sort( function( a, b ) { return a.label > b.label ? 1 : a.label < b.label ? -1 : 0; }); - - // add additional options extensions.unshift({ 0 : 'Sequences', 1 : 'Sequences' @@ -158,8 +114,6 @@ define(['utils/utils', 'mvc/tool/tool-form-base'], 0 : 'Leave unchanged', 1 : '__empty__' }); - - // create custom sub section var input_config = { title : 'Configure Output: \'' + output_id + '\'', type : 'section', @@ -242,53 +196,40 @@ define(['utils/utils', 'mvc/tool/tool-form-base'], }; // visit input nodes and enrich by name/value pairs from server data - function visit (head, head_list) { + function visit ( head, head_list ) { head_list = head_list || []; - head_list.push(head); - for (var i in head.inputs) { - var input = head.inputs[i]; + head_list.push( head ); + for ( var i in head.inputs ) { + var input = head.inputs[ i ]; var action = input.action; - if (action) { - // construct identifier as expected by backend + if ( action ) { input.name = 'pja__' + output_id + '__' + input.action; - if (input.pja_arg) { + if ( input.pja_arg ) { input.name += '__' + input.pja_arg; } - - // modify names of payload arguments - if (input.payload) { - for (var p_id in input.payload) { - var p = input.payload[p_id]; - input.payload[input.name + '__' + p_id] = p; + if ( input.payload ) { + for ( var p_id in input.payload ) { + var p = input.payload[ p_id ]; + input.payload[ input.name + '__' + p_id ] = p; delete p; } } - - // access/verify existence of value - var d = self.post_job_actions[input.action + output_id]; - if (d) { - // mark as expanded - for (var j in head_list) { - head_list[j].expanded = true; + var d = self.post_job_actions[ input.action + output_id ]; + if ( d ) { + for ( var j in head_list ) { + head_list[ j ].expanded = true; } - - // update input field value - if (input.pja_arg) { - input.value = d.action_arguments && d.action_arguments[input.pja_arg] || input.value; + if ( input.pja_arg ) { + input.value = d.action_arguments && d.action_arguments[ input.pja_arg ] || input.value; } else { input.value = 'true'; } } } - // continue with sub section - if (input.inputs) { - visit(input, head_list.slice(0)); - } + input.inputs && visit( input, head_list.slice( 0 ) ); } } - visit(input_config); - - // return final configuration + visit( input_config ); return input_config; } }); diff --git a/client/galaxy/scripts/mvc/tool/tool-form.js b/client/galaxy/scripts/mvc/tool/tool-form.js index b615bdee823..693f0617452 100644 --- a/client/galaxy/scripts/mvc/tool/tool-form.js +++ b/client/galaxy/scripts/mvc/tool/tool-form.js @@ -1,16 +1,14 @@ -/** - This is the regular tool form. -*/ +/* This is the regular tool form */ define([ 'utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-modal', 'mvc/tool/tool-form-base' ], function( Utils, Ui, Modal, ToolFormBase ) { - var View = ToolFormBase.extend({ + var View = Backbone.View.extend({ initialize: function( options ) { var self = this; - options.listen_to_history = true; - options.always_refresh = false; this.modal = parent.Galaxy.modal || new Modal.View(); - ToolFormBase.prototype.initialize.call( this, Utils.merge({ - customize: function( options ) { + this.form = new ToolFormBase( Utils.merge({ + listen_to_history : true, + always_refresh : false, + customize : function( options ) { // build execute button options.buttons = { execute: execute_btn = new Ui.Button({ @@ -21,10 +19,10 @@ define([ 'utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-modal', 'mvc/tool/tool-form floating : 'clear', onclick : function() { execute_btn.wait(); - self.portlet.disable(); + self.form.portlet.disable(); self.submit( options, function() { execute_btn.unwait(); - self.portlet.enable(); + self.form.portlet.enable(); } ); } }) @@ -44,6 +42,9 @@ define([ 'utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-modal', 'mvc/tool/tool-form } } }, options ) ); + this.deferred = this.form.deferred; + this.setElement( '' ); + this.$el.append( this.form.$el ); }, /** Submit a regular job. @@ -55,9 +56,9 @@ define([ 'utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-modal', 'mvc/tool/tool-form var job_def = { tool_id : options.id, tool_version : options.version, - inputs : this.data.create() + inputs : this.form.data.create() } - this.trigger( 'reset' ); + this.form.trigger( 'reset' ); if ( !self.validate( job_def ) ) { Galaxy.emit.debug( 'tool-form::submit()', 'Submission canceled. Validation failed.' ); callback && callback(); @@ -86,9 +87,9 @@ define([ 'utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-modal', 'mvc/tool/tool-form Galaxy.emit.debug( 'tool-form::submit', 'Submission failed.', response ); var input_found = false; if ( response && response.err_data ) { - var error_messages = self.data.matchResponse( response.err_data ); + var error_messages = self.form.data.matchResponse( response.err_data ); for (var input_id in error_messages) { - self.highlight( input_id, error_messages[ input_id ]); + self.form.highlight( input_id, error_messages[ input_id ]); input_found = true; break; } @@ -97,11 +98,7 @@ define([ 'utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-modal', 'mvc/tool/tool-form self.modal.show({ title : 'Job submission failed', body : ( response && response.err_msg ) || self._templateError( job_def ), - buttons : { - 'Close' : function() { - self.modal.hide(); - } - } + buttons : { 'Close' : function() { self.modal.hide() } } }); } } @@ -117,15 +114,15 @@ define([ 'utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-modal', 'mvc/tool/tool-form var batch_src = null; for ( var job_input_id in job_inputs ) { var input_value = job_inputs[ job_input_id ]; - var input_id = this.data.match( job_input_id ); - var input_field = this.field_list[ input_id ]; - var input_def = this.input_list[ input_id ]; + var input_id = this.form.data.match( job_input_id ); + var input_field = this.form.field_list[ input_id ]; + var input_def = this.form.input_list[ input_id ]; if ( !input_id || !input_def || !input_field ) { Galaxy.emit.debug('tool-form::validate()', 'Retrieving input objects failed.'); continue; } if ( !input_def.optional && input_value == null ) { - this.highlight( input_id ); + this.form.highlight( input_id ); return false; } if ( input_value && input_value.batch ) { @@ -135,14 +132,14 @@ define([ 'utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-modal', 'mvc/tool/tool-form if ( batch_src === null ) { batch_src = src; } else if ( batch_src !== src ) { - this.highlight( input_id, 'Please select either dataset or dataset list fields for all batch mode fields.' ); + this.form.highlight( input_id, 'Please select either dataset or dataset list fields for all batch mode fields.' ); return false; } } if ( batch_n === -1 ) { batch_n = n; } else if ( batch_n !== n ) { - this.highlight( input_id, 'Please make sure that you select the same number of inputs for all batch mode fields. This field contains ' + n + ' selection(s) while a previous field contains ' + batch_n + '.' ); + this.form.highlight( input_id, 'Please make sure that you select the same number of inputs for all batch mode fields. This field contains ' + n + ' selection(s) while a previous field contains ' + batch_n + '.' ); return false; } } @@ -155,7 +152,7 @@ define([ 'utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-modal', 'mvc/tool/tool-form var njobs = response.jobs.length; var njobs_text = njobs == 1 ? '1 job has' : njobs + ' jobs have'; var $message = $( '' ).addClass( 'donemessagelarge' ) - .append( $( '' ).text( njobs_text + ' been successfully added to the queue - resulting in the following datasets:' ) ); + .append( $( '' ).text( njobs_text + ' been successfully added to the queue - resulting in the following datasets:' ) ); _.each( response.outputs, function( output ) { $message.append( $( '' ).addClass( 'messagerow' ).append( $( '' ).text( output.hid + ': ' + output.name ) ) ); }); @@ -168,8 +165,8 @@ define([ 'utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-modal', 'mvc/tool/tool-form _templateError: function( response ) { return $( '' ).addClass( 'errormessagelarge' ) - .append( $( '' ).text( 'The server could not complete the request. Please contact the Galaxy Team if this error persists.' ) ) - .append( $( '' ).text( JSON.stringify( response, null, 4 ) ) ); + .append( $( '' ).text( 'The server could not complete the request. Please contact the Galaxy Team if this error persists.' ) ) + .append( $( '' ).text( JSON.stringify( response, null, 4 ) ) ); } }); diff --git a/client/galaxy/scripts/mvc/ui/ui-portlet.js b/client/galaxy/scripts/mvc/ui/ui-portlet.js index b12dbe552c0..65720344e63 100644 --- a/client/galaxy/scripts/mvc/ui/ui-portlet.js +++ b/client/galaxy/scripts/mvc/ui/ui-portlet.js @@ -25,7 +25,6 @@ var View = Backbone.View.extend({ this.$title_icon = this.$( '.portlet-title-icon' ); this.$header = this.$( '.portlet-header' ); this.$content = this.$( '.portlet-content' ); - this.$footer = this.$( '.portlet-footer' ); this.$backdrop = this.$( '.portlet-backdrop' ); this.$buttons = this.$( '.portlet-buttons' ); this.$operations = this.$( '.portlet-operations' ); @@ -112,11 +111,6 @@ var View = Backbone.View.extend({ return this.$body; }, - /** Return footer element */ - footer: function() { - return this.$footer; - }, - /** Show portlet */ show: function(){ this.visible = true; @@ -165,7 +159,6 @@ var View = Backbone.View.extend({ this.collapsed = true; this.$content.height( '0%' ); this.$body.hide(); - this.$footer.hide(); this.collapsible_button.setIcon( 'fa-eye-slash' ); }, @@ -174,7 +167,6 @@ var View = Backbone.View.extend({ this.collapsed = false; this.$content.height( '100%' ); this.$body.fadeIn( 'fast' ); - this.$footer.fadeIn( 'fast' ); this.collapsible_button.setIcon( 'fa-eye' ); }, @@ -197,7 +189,6 @@ var View = Backbone.View.extend({ .append( $( '' ).addClass( 'portlet-content' ) .append( $( '' ).addClass( 'portlet-body' ) ) .append( $( '' ).addClass( 'portlet-buttons' ) ) ) - .append( $( '' ).addClass( 'portlet-footer' ) ) .append( $( '' ).addClass( 'portlet-backdrop' ) ); } }); diff --git a/client/galaxy/scripts/mvc/ui/ui-select-content.js b/client/galaxy/scripts/mvc/ui/ui-select-content.js index aafa41ee93f..0d1c9112b89 100644 --- a/client/galaxy/scripts/mvc/ui/ui-select-content.js +++ b/client/galaxy/scripts/mvc/ui/ui-select-content.js @@ -190,7 +190,7 @@ var View = Backbone.View.extend({ var button_width = 0; if ( this.fields.length > 1 ) { this.$el.append( this.button_type.$el ); - button_width = Math.max( 0, this.fields.length * 35 ) + 'px'; + button_width = Math.max( 0, this.fields.length * 36 ) + 'px'; } _.each( this.fields, function( field ) { self.$el.append( field.$el.css( { 'margin-left': button_width } ) ); diff --git a/client/galaxy/scripts/utils/utils.js b/client/galaxy/scripts/utils/utils.js index 1cac029c26a..9abffc621d4 100644 --- a/client/galaxy/scripts/utils/utils.js +++ b/client/galaxy/scripts/utils/utils.js @@ -105,8 +105,8 @@ function get (options) { top.__utils__get__[cache_key] = response; options.success && options.success(response); }, - error : function(response) { - options.error && options.error(response); + error : function(response, status) { + options.error && options.error(response, status); } }); } @@ -159,7 +159,7 @@ function request (options) { } catch (e) { response_text = response.responseText; } - options.error && options.error(response_text, response); + options.error && options.error(response_text, response.status); }).always(function() { options.complete && options.complete(); }); diff --git a/client/galaxy/style/less/ui.less b/client/galaxy/style/less/ui.less index 77279e9b782..4f8416a3239 100644 --- a/client/galaxy/style/less/ui.less +++ b/client/galaxy/style/less/ui.less @@ -299,6 +299,7 @@ margin-top: @ui-margin-vertical; border-color: transparent !important; box-shadow: none !important; + height: auto !important; } } .ui-form-element-disabled { @@ -435,11 +436,6 @@ .portlet-content.nopadding, .portlet-body.nopadding { padding: 0px; } - .portlet-footer { - margin-top: 5px; - padding-left: 10px; - padding-right: 10px; - } .portlet-backdrop { display: none; z-index: 10; @@ -473,9 +469,6 @@ .portlet-content { padding-right: 0px; } - .portlet-footer { - margin-top: 0px; - } } .ui-portlet-panel { @@ -498,6 +491,7 @@ .ui-portlet-narrow { &:extend(.ui-portlet-limited all); border: none; + margin-bottom: @ui-margin-vertical-large; .portlet-header { border-radius: @border-radius-base; .portlet-operations { diff --git a/lib/galaxy/managers/workflows.py b/lib/galaxy/managers/workflows.py index 64117c7d560..328f760eb2f 100644 --- a/lib/galaxy/managers/workflows.py +++ b/lib/galaxy/managers/workflows.py @@ -14,15 +14,15 @@ from galaxy import util from galaxy import exceptions from galaxy.model.item_attrs import UsesAnnotations from galaxy.workflow import modules - from .base import decode_id # For WorkflowContentManager from galaxy.util.sanitize_html import sanitize_html from galaxy.workflow.steps import attach_ordered_steps -from galaxy.workflow.modules import module_factory, is_tool_module_type, ToolModule -from galaxy.tools.parameters.basic import DataToolParameter, DataCollectionToolParameter -from galaxy.tools.parameters import visit_input_values +from galaxy.workflow.modules import module_factory, is_tool_module_type, ToolModule, WorkflowModuleInjector, MissingToolException +from galaxy.tools.parameters.basic import DataToolParameter, DataCollectionToolParameter, workflow_building_modes +from galaxy.tools.parameters import visit_input_values, params_to_incoming +from galaxy.jobs.actions.post import ActionBox from galaxy.web import url_for log = logging.getLogger( __name__ ) @@ -334,9 +334,81 @@ class WorkflowContentsManager(UsesAnnotations): return self._workflow_to_dict_instance( stored, legacy=True ) elif style == "instance": return self._workflow_to_dict_instance( stored, legacy=False ) + elif style == "run": + return self._workflow_to_dict_run( trans, stored ) else: return self._workflow_to_dict_export( trans, stored ) + def _workflow_to_dict_run( self, trans, stored ): + """ + Builds workflow dictionary used by run workflow form + """ + workflow = stored.latest_workflow + trans.workflow_building_mode = workflow_building_modes.USE_HISTORY + module_injector = WorkflowModuleInjector( trans ) + has_upgrade_messages = False + step_version_changes = [] + missing_tools = [] + errors = {} + for step in workflow.steps: + try: + module_injector.inject( step, steps=workflow.steps ) + except MissingToolException: + if step.tool_id not in missing_tools: + missing_tools.append( step.tool_id ) + continue + if step.upgrade_messages: + has_upgrade_messages = True + if step.type == 'tool' or step.type is None: + if step.module.version_changes: + step_version_changes.extend( step.module.version_changes ) + if step.tool_errors: + errors[ step.id ] = step.tool_errors + if missing_tools: + workflow.annotation = self.get_item_annotation_str( trans.sa_session, trans.user, workflow ) + raise exceptions.MessageException( 'Following tools missing: %s' % missing_tools ) + workflow.annotation = self.get_item_annotation_str( trans.sa_session, trans.user, workflow ) + step_models = [] + for i, step in enumerate( workflow.steps ): + step_model = None + if step.type == 'tool': + incoming = {} + tool = trans.app.toolbox.get_tool( step.tool_id ) + params_to_incoming( incoming, tool.inputs, step.state.inputs, trans.app ) + step_model = tool.to_json( trans, incoming, workflow_building_mode=workflow_building_modes.USE_HISTORY ) + step_model[ 'post_job_actions' ] = [{ + 'short_str' : ActionBox.get_short_str( pja ), + 'action_type' : pja.action_type, + 'output_name' : pja.output_name, + 'action_arguments' : pja.action_arguments + } for pja in step.post_job_actions ] + else: + inputs = step.module.get_runtime_inputs( connections=step.output_connections ) + step_model = { + 'name' : step.module.name, + 'inputs' : [ input.to_dict( trans ) for input in inputs.itervalues() ] + } + step_model[ 'step_id' ] = step.id + step_model[ 'step_type' ] = step.type + step_model[ 'output_connections' ] = [ { + 'input_step_id' : oc.input_step_id, + 'output_step_id' : oc.output_step_id, + 'input_name' : oc.input_name, + 'output_name' : oc.output_name + } for oc in step.output_connections ] + if step.annotations: + step_model[ 'annotation' ] = step.annotations[ 0 ].annotation + if step.upgrade_messages: + step_model[ 'messages' ] = step.upgrade_messages + step_models.append( step_model ) + return { + 'id' : trans.app.security.encode_id( stored.id ), + 'name' : stored.name, + 'steps' : step_models, + 'step_version_changes' : step_version_changes, + 'has_upgrade_messages' : has_upgrade_messages + } + def _workflow_to_dict_editor(self, trans, stored): """ """ diff --git a/lib/galaxy/tools/parameters/basic.py b/lib/galaxy/tools/parameters/basic.py index 9125e14f00f..75b8a0ccb14 100644 --- a/lib/galaxy/tools/parameters/basic.py +++ b/lib/galaxy/tools/parameters/basic.py @@ -10,7 +10,6 @@ from six import string_types from xml.etree.ElementTree import XML from galaxy import util -from galaxy.web import form_builder from galaxy.util import string_as_bool, sanitize_param, unicodify from galaxy.util.expressions import ExpressionContext from sanitize import ToolParameterSanitizer @@ -21,25 +20,24 @@ from ..parameters import history_query from ..parameters import dynamic_options from .dataset_matcher import DatasetMatcher from .dataset_matcher import DatasetCollectionMatcher -# For BaseURLToolParameter from galaxy.web import url_for from galaxy.util.dictifiable import Dictifiable import galaxy.model from galaxy.util.bunch import Bunch -log = logging.getLogger(__name__) +log = logging.getLogger( __name__ ) workflow_building_modes = Bunch( DISABLED=False, ENABLED=True, USE_HISTORY=1 ) WORKFLOW_PARAMETER_REGULAR_EXPRESSION = re.compile( '''\$\{.+?\}''' ) -def contains_workflow_parameter(value, search=False): +def contains_workflow_parameter( value, search=False ): if not isinstance( value, string_types ): return False - if search and WORKFLOW_PARAMETER_REGULAR_EXPRESSION.search(value): + if search and WORKFLOW_PARAMETER_REGULAR_EXPRESSION.search( value ): return True - if not search and WORKFLOW_PARAMETER_REGULAR_EXPRESSION.match(value): + if not search and WORKFLOW_PARAMETER_REGULAR_EXPRESSION.match( value ): return True return False @@ -56,11 +54,11 @@ class ToolParameter( object, Dictifiable ): input_source = ensure_input_source(input_source) self.tool = tool self.refresh_on_change_values = [] - self.argument = input_source.get("argument") + self.argument = input_source.get( "argument" ) self.name = ToolParameter.parse_name( input_source ) - self.type = input_source.get("type") - self.hidden = input_source.get("hidden", False) - self.refresh_on_change = input_source.get_bool("refresh_on_change", False) + self.type = input_source.get( "type" ) + self.hidden = input_source.get( "hidden", False ) + self.refresh_on_change = input_source.get_bool( "refresh_on_change", False ) self.optional = input_source.parse_optional() self.is_dynamic = False self.label = input_source.parse_label() @@ -70,14 +68,6 @@ class ToolParameter( object, Dictifiable ): self.sanitizer = ToolParameterSanitizer.from_element( sanitizer_elem ) else: self.sanitizer = None - try: - # These don't do anything right? These we should - # delete these two lines and eliminate checks for - # self.repeat in this file. -John - self.repeat = input_source.elem().get("repeat", None) - self.condition = input_source.elem().get( "condition", None ) - except Exception: - self.repeat = None self.validators = [] for elem in input_source.parse_validator_elems(): self.validators.append( validation.Validator.from_element( self, elem ) ) @@ -91,16 +81,6 @@ class ToolParameter( object, Dictifiable ): """Return user friendly name for the parameter""" return self.label if self.label else self.name - def get_html_field( self, trans=None, value=None, other_values={} ): - raise TypeError( "Abstract Method" ) - - def get_html( self, trans=None, value=None, other_values={} ): - """ - Returns the html widget corresponding to the parameter. - Optionally attempt to retain the current value specific by 'value' - """ - return self.get_html_field( trans, value, other_values ).get_html() - def from_json( self, value, trans=None, other_values={} ): """ Convert a value from an HTML POST into the parameters preferred value @@ -140,12 +120,12 @@ class ToolParameter( object, Dictifiable ): if isinstance( value, RuntimeValue ): return { '__class__': 'RuntimeValue' } elif isinstance( value, dict ): - if value.get('__class__') == 'RuntimeValue': + if value.get( '__class__' ) == 'RuntimeValue': return value return self.to_json( value, app, use_security ) def value_from_basic( self, value, app, ignore_errors=False ): - # Handle Runtime values (valid for any parameter?) + # Handle Runtime and Unvalidated values if isinstance( value, dict ) and value.get( '__class__' ) == 'RuntimeValue': return RuntimeValue() elif isinstance( value, dict ) and value.get( '__class__' ) == 'UnvalidatedValue': @@ -182,12 +162,12 @@ class ToolParameter( object, Dictifiable ): return value def validate( self, value, trans=None ): - if value in ["", None] and self.optional: + if value in [ "", None ] and self.optional: return for validator in self.validators: validator.validate( value, trans ) - def to_dict( self, trans, view='collection', value_mapper=None, other_values={} ): + def to_dict( self, trans, other_values={} ): """ to_dict tool parameter. This can be overridden by subclasses. """ tool_dict = super( ToolParameter, self ).to_dict() tool_dict[ 'model_class' ] = self.__class__.__name__ @@ -211,7 +191,7 @@ class ToolParameter( object, Dictifiable ): return parameter_types[ param_type ]( tool, param ) @classmethod - def parse_name(cls, input_source): + def parse_name( cls, input_source ): name = input_source.get( 'name' ) if name is None: argument = input_source.get( 'argument' ) @@ -226,29 +206,21 @@ class TextToolParameter( ToolParameter ): """ Parameter that can take on any text value. - >>> p = TextToolParameter( None, XML( '' ) ) + >>> from galaxy.util.bunch import Bunch + >>> trans = Bunch() + >>> p = TextToolParameter( None, XML( '' ) ) >>> print p.name - blah - >>> print p.get_html() - - >>> print p.get_html( value="meh" ) - + _name + >>> d = p.to_dict( trans ) + >>> [ "%s: %s" % ( key, d[ key ] ) for key in sorted( d ) ] + ['area: False', 'argument: None', 'help: ', 'hidden: False', 'is_dynamic: False', 'label: ', 'model_class: TextToolParameter', 'name: _name', 'optional: False', 'refresh_on_change: False', 'type: text', 'value: default'] """ def __init__( self, tool, input_source ): input_source = ensure_input_source(input_source) ToolParameter.__init__( self, tool, input_source ) - self.size = input_source.get( 'size' ) self.value = input_source.get( 'value' ) self.area = input_source.get_bool( 'area', False ) - def get_html_field( self, trans=None, value=None, other_values={} ): - if value is None: - value = self.value - if self.area: - return form_builder.TextArea( self.name, self.size, value ) - else: - return form_builder.TextField( self.name, self.size, value ) - def to_json( self, value, app, use_security ): """Convert a value to a string representation suitable for persisting""" if value is None: @@ -265,10 +237,9 @@ class TextToolParameter( ToolParameter ): def get_initial_value( self, trans, other_values ): return self.value - def to_dict( self, trans, view='collection', value_mapper=None, other_values={} ): + def to_dict( self, trans, other_values={} ): d = super(TextToolParameter, self).to_dict(trans) d['area'] = self.area - d['size'] = self.size return d @@ -277,14 +248,15 @@ class IntegerToolParameter( TextToolParameter ): Parameter that takes an integer value. >>> from galaxy.util.bunch import Bunch >>> trans = Bunch( history=Bunch(), workflow_building_mode=True ) - >>> p = IntegerToolParameter( None, XML( '' ) ) + >>> p = IntegerToolParameter( None, XML( '' ) ) >>> print p.name - blah - >>> print p.get_html() - + _name + >>> d = p.to_dict( trans ) + >>> [ "%s: %s" % ( key, d[ key ] ) for key in sorted( d ) ] + ['area: False', 'argument: None', 'help: ', 'hidden: False', 'is_dynamic: False', 'label: ', 'max: None', 'min: None', 'model_class: IntegerToolParameter', 'name: _name', 'optional: False', 'refresh_on_change: False', 'type: integer', 'value: 10'] >>> type( p.from_json( "10", trans ) )"+this.formattedReference()+"
"),this},formattedReference:function(){var e=this.model,t=e.entryType(),i=e.fields(),n="",s=this._asSentence((i.author?i.author:"")+(i.year?" ("+i.year+")":""))+" ",o=i.title||"",a=i.pages?"pp. "+i.pages:"",r=i.address;if("article"==t){var l=(i.volume?i.volume:"")+(i.number?" ("+i.number+")":"")+(a?", "+a:"");n=s+this._asSentence(o)+(i.journal?"In "+i.journal+", ":"")+this._asSentence(l)+this._asSentence(i.address)+""}else n="inproceedings"==t||"proceedings"==t?s+this._asSentence(o)+(i.booktitle?"In "+i.booktitle+", ":"")+(a?a:"")+(r?", "+r:"")+".":"mastersthesis"==t||"phdthesis"==t?s+this._asSentence(o)+(i.howpublished?i.howpublished+". ":"")+(i.note?i.note+".":""):"techreport"==t?s+this._asSentence(o)+this._asSentence(i.institution)+this._asSentence(i.number)+this._asSentence(i.type):"book"==t||"inbook"==t||"incollection"==t?s+" "+this._formatBookInfo(i):s+" "+this._asSentence(o)+this._asSentence(i.howpublished)+this._asSentence(i.note);var c="";i.doi&&(c="http://dx.doi.org/"+i.doi,n+='[doi:'+i.doi+"]");var d=i.url||c;return d&&(n+='[Link]'),n},_formatBookInfo:function(e){var t="";return e.chapter&&(t+=e.chapter+" in "),e.title&&(t+=""+e.title+""),e.editor&&(t+=", Edited by "+e.editor+", "),e.publisher&&(t+=", "+e.publisher),e.pages&&(t+=", pp. "+e.pages),e.series&&(t+=", "+e.series+""),e.volume&&(t+=", Vol."+e.volume),e.issn&&(t+=", ISBN: "+e.issn),t+"."},_asSentence:function(e){return e&&e.trim()?e+". ":""}}),s=o.View.extend({el:"#citations",initialize:function(){this.listenTo(this.collection,"add",this.renderCitation)},events:{"click .citations-to-bibtex":"showBibtex","click .citations-to-formatted":"showFormatted"},renderCitation:function(e){var t=new n({model:e});this.$(".citations-formatted").append(t.render().el);var i=this.$(".citations-bibtex-text");i.val(i.val()+"\n\r"+e.attributes.content)},render:function(){this.$el.html(this.citationsElement()),this.collection.each(function(e){this.renderCitation(e)},this),this.showFormatted()},showBibtex:function(){this.$(".citations-to-formatted").show(),this.$(".citations-to-bibtex").hide(),this.$(".citations-bibtex").show(),this.$(".citations-formatted").hide(),this.$(".citations-bibtex-text").select()},showFormatted:function(){this.$(".citations-to-formatted").hide(),this.$(".citations-to-bibtex").show(),this.$(".citations-bibtex").hide(),this.$(".citations-formatted").show()},partialWarningElement:function(){return this.collection.partial?['"+JSON.stringify(i.responseJSON)+"":content+=": "+n}t._showAlert(content,"alert-danger")},events:{"click .more-help":"_clickMoreHelp","click .less-help":"_clickLessHelp","click .main-help":"_toggleHelp","click .header .alert button":"_hideAlert","click .reset":"reset","click .clear-selected":"clearSelectedElements","click .collection-elements":"clearSelectedElements","dragover .collection-elements":"_dragoverElements","drop .collection-elements":"_dropElements","collection-element.dragstart .collection-elements":"_elementDragstart","collection-element.dragend .collection-elements":"_elementDragend","change .collection-name":"_changeName","keydown .collection-name":"_nameCheckForEnter","click .cancel-create":function(e){"function"==typeof this.oncancel&&this.oncancel.call(this)},"click .create-collection":"_clickCreate"},_clickMoreHelp:function(e){e.stopPropagation(),this.$(".main-help").addClass("expanded"),this.$(".more-help").hide()},_clickLessHelp:function(e){e.stopPropagation(),this.$(".main-help").removeClass("expanded"),this.$(".more-help").show()},_toggleHelp:function(e){e.stopPropagation(),this.$(".main-help").toggleClass("expanded"),this.$(".more-help").toggle()},_showAlert:function(e,t){t=t||"alert-danger",this.$(".main-help").hide(),this.$(".header .alert").attr("class","alert alert-dismissable").addClass(t).show().find(".alert-message").html(e)},_hideAlert:function(e){this.$(".main-help").show(),this.$(".header .alert").hide()},reset:function(){this._instanceSetUp(),this._elementsSetUp(),this.render()},clearSelectedElements:function(e){this.$(".collection-elements .collection-element").removeClass("selected"),this.$(".collection-elements-controls > .clear-selected").hide()},_dragoverElements:function(e){e.preventDefault();var t=this.$list();this._checkForAutoscroll(t,e.originalEvent.clientY);var i=this._getNearestElement(e.originalEvent.clientY);this.$(".element-drop-placeholder").remove();var n=r('');i.length?i.before(n):t.append(n)},_checkForAutoscroll:function(e,t){var i=2,n=e.offset(),s=e.scrollTop(),o=t-n.top,a=n.top+e.outerHeight()-t;o>=0&&o
",c(["Collections of datasets are permanent, ordered lists of datasets that can be passed to tools and ","workflows in order to have analyses done on each member of the entire group. This interface allows ","you to create a collection and re-order the final collection."].join("")),"
","",c(['Once your collection is complete, enter a name and ','click "Create list".'].join("")),"
"].join("")),invalidElements:a.template([c("The following selections could not be included due to problems:"),"<%= dataset.peek %>',"<% } %>","<% } %>","
"+this.formattedReference()+"
"),this},formattedReference:function(){var e=this.model,t=e.entryType(),i=e.fields(),n="",s=this._asSentence((i.author?i.author:"")+(i.year?" ("+i.year+")":""))+" ",o=i.title||"",a=i.pages?"pp. "+i.pages:"",r=i.address;if("article"==t){var l=(i.volume?i.volume:"")+(i.number?" ("+i.number+")":"")+(a?", "+a:"");n=s+this._asSentence(o)+(i.journal?"In "+i.journal+", ":"")+this._asSentence(l)+this._asSentence(i.address)+""}else n="inproceedings"==t||"proceedings"==t?s+this._asSentence(o)+(i.booktitle?"In "+i.booktitle+", ":"")+(a?a:"")+(r?", "+r:"")+".":"mastersthesis"==t||"phdthesis"==t?s+this._asSentence(o)+(i.howpublished?i.howpublished+". ":"")+(i.note?i.note+".":""):"techreport"==t?s+this._asSentence(o)+this._asSentence(i.institution)+this._asSentence(i.number)+this._asSentence(i.type):"book"==t||"inbook"==t||"incollection"==t?s+" "+this._formatBookInfo(i):s+" "+this._asSentence(o)+this._asSentence(i.howpublished)+this._asSentence(i.note);var c="";i.doi&&(c="http://dx.doi.org/"+i.doi,n+='[doi:'+i.doi+"]");var d=i.url||c;return d&&(n+='[Link]'),n},_formatBookInfo:function(e){var t="";return e.chapter&&(t+=e.chapter+" in "),e.title&&(t+=""+e.title+""),e.editor&&(t+=", Edited by "+e.editor+", "),e.publisher&&(t+=", "+e.publisher),e.pages&&(t+=", pp. "+e.pages),e.series&&(t+=", "+e.series+""),e.volume&&(t+=", Vol."+e.volume),e.issn&&(t+=", ISBN: "+e.issn),t+"."},_asSentence:function(e){return e&&e.trim()?e+". ":""}}),s=o.View.extend({el:"#citations",initialize:function(){this.listenTo(this.collection,"add",this.renderCitation)},events:{"click .citations-to-bibtex":"showBibtex","click .citations-to-formatted":"showFormatted"},renderCitation:function(e){var t=new n({model:e});this.$(".citations-formatted").append(t.render().el);var i=this.$(".citations-bibtex-text");i.val(i.val()+"\n\r"+e.attributes.content)},render:function(){this.$el.html(this.citationsElement()),this.collection.each(function(e){this.renderCitation(e)},this),this.showFormatted()},showBibtex:function(){this.$(".citations-to-formatted").show(),this.$(".citations-to-bibtex").hide(),this.$(".citations-bibtex").show(),this.$(".citations-formatted").hide(),this.$(".citations-bibtex-text").select()},showFormatted:function(){this.$(".citations-to-formatted").hide(),this.$(".citations-to-bibtex").show(),this.$(".citations-bibtex").hide(),this.$(".citations-formatted").show()},partialWarningElement:function(){return this.collection.partial?['"+JSON.stringify(i.responseJSON)+"":content+=": "+n}t._showAlert(content,"alert-danger")},events:{"click .more-help":"_clickMoreHelp","click .less-help":"_clickLessHelp","click .main-help":"_toggleHelp","click .header .alert button":"_hideAlert","click .reset":"reset","click .clear-selected":"clearSelectedElements","click .collection-elements":"clearSelectedElements","dragover .collection-elements":"_dragoverElements","drop .collection-elements":"_dropElements","collection-element.dragstart .collection-elements":"_elementDragstart","collection-element.dragend .collection-elements":"_elementDragend","change .collection-name":"_changeName","keydown .collection-name":"_nameCheckForEnter","click .cancel-create":function(e){"function"==typeof this.oncancel&&this.oncancel.call(this)},"click .create-collection":"_clickCreate"},_clickMoreHelp:function(e){e.stopPropagation(),this.$(".main-help").addClass("expanded"),this.$(".more-help").hide()},_clickLessHelp:function(e){e.stopPropagation(),this.$(".main-help").removeClass("expanded"),this.$(".more-help").show()},_toggleHelp:function(e){e.stopPropagation(),this.$(".main-help").toggleClass("expanded"),this.$(".more-help").toggle()},_showAlert:function(e,t){t=t||"alert-danger",this.$(".main-help").hide(),this.$(".header .alert").attr("class","alert alert-dismissable").addClass(t).show().find(".alert-message").html(e)},_hideAlert:function(e){this.$(".main-help").show(),this.$(".header .alert").hide()},reset:function(){this._instanceSetUp(),this._elementsSetUp(),this.render()},clearSelectedElements:function(e){this.$(".collection-elements .collection-element").removeClass("selected"),this.$(".collection-elements-controls > .clear-selected").hide()},_dragoverElements:function(e){e.preventDefault();var t=this.$list();this._checkForAutoscroll(t,e.originalEvent.clientY);var i=this._getNearestElement(e.originalEvent.clientY);this.$(".element-drop-placeholder").remove();var n=r('');i.length?i.before(n):t.append(n)},_checkForAutoscroll:function(e,t){var i=2,n=e.offset(),s=e.scrollTop(),o=t-n.top,a=n.top+e.outerHeight()-t;o>=0&&o
",c(["Collections of datasets are permanent, ordered lists of datasets that can be passed to tools and ","workflows in order to have analyses done on each member of the entire group. This interface allows ","you to create a collection and re-order the final collection."].join("")),"
","",c(['Once your collection is complete, enter a name and ','click "Create list".'].join("")),"
"].join("")),invalidElements:a.template([c("The following selections could not be included due to problems:"),"<%= dataset.peek %>',"<% } %>","<% } %>","
| Name | Size | Created |
|---|
h)return 1}return 0}return e}.apply(t,n),!(void 0!==s&&(e.exports=s))},function(e,t,i){(function(e,t){!function(i){e.event.props.push("dataTransfer"),i.uploadpost=function(t){var n=i.extend({},{data:{},success:function(){},error:function(){},progress:function(){},url:null,maxfilesize:2048,error_filesize:"File exceeds 2GB. Please use a FTP client.",error_default:"Please make sure the file is available.",error_server:"Upload request failed.",error_login:"Uploads require you to log in."},t),s=n.data;if(s.error_message)return void n.error(s.error_message);var o=new FormData;for(var a in s.payload)o.append(a,s.payload[a]);var r=0;for(var a in s.files){var l=s.files[a];o.append(l.name,l.file,l.file.name),r+=l.file.size}return r>1048576*n.maxfilesize?void n.error(n.error_filesize):(xhr=new XMLHttpRequest,xhr.open("POST",n.url,!0),xhr.setRequestHeader("Accept","application/json"),xhr.setRequestHeader("Cache-Control","no-cache"),xhr.setRequestHeader("X-Requested-With","XMLHttpRequest"),xhr.onreadystatechange=function(){if(xhr.readyState==xhr.DONE){var t=null;if(xhr.responseText)try{t=e.parseJSON(xhr.responseText)}catch(i){t=xhr.responseText}if(xhr.status<200||xhr.status>299){var s=xhr.statusText;403==xhr.status?s=n.error_login:0==xhr.status?s=n.error_server:s||(s=n.error_default),n.error(s+" ("+xhr.status+")")}else n.success(t)}},xhr.upload.addEventListener("progress",function(e){e.lengthComputable&&n.progress(Math.round(100*e.loaded/e.total))},!1),Galaxy.emit.debug("uploadbox::uploadpost()","Posting following data.",n),void xhr.send(o))},i.fn.uploadinput=function(e){var t=this,n=i.extend({},{ondragover:function(){},ondragleave:function(){},onchange:function(){},multiple:!1},e),s=i('");return t.append(s.change(function(e){n.onchange(e.target.files),i(this).val("")})),t.on("drop",function(e){n.ondragleave(e),e.dataTransfer&&(n.onchange(e.dataTransfer.files),e.preventDefault())}),t.on("dragover",function(e){e.preventDefault(),n.ondragover(e)}),t.on("dragleave",function(e){e.stopPropagation(),n.ondragleave(e)}),{dialog:function(){s.trigger("click")}}},i.fn.uploadbox=function(e){function n(e){if(e&&e.length&&!m){var i=g;return t.each(e,function(e,i){"new"!==e.mode&&t.filter(p,function(t){return t.name===e.name&&t.size===e.size}).length&&(e.duplicate=!0)}),t.each(e,function(e){if(!e.duplicate){var t=String(g++);p[t]=e,u.announce(t,p[t]),f++}}),i}}function s(e){p[e]&&(delete p[e],f--)}function o(){if(0==f||v)return v=!1,m=!1,void u.complete();m=!0;var e=-1;for(var t in p){e=t;break}p[e];s(e),i.uploadpost({url:u.url,data:u.initialize(e),success:function(t){u.success(e,t),o()},error:function(t){u.error(e,t),o()},progress:function(t){u.progress(e,t)}})}function a(){_.dialog()}function r(e){for(e in p)s(e)}function l(){m||(m=!0,o())}function c(){v=!0}function d(e){return u=i.extend({},u,e)}function h(){return window.File&&window.FormData&&window.XMLHttpRequest&&window.FileList}var u=i.extend({},{dragover:function(){},dragleave:function(){},announce:function(e){},initialize:function(e){},progress:function(e,t){},success:function(e,t){},error:function(e,t){alert(t)},complete:function(){}},e),p={},g=0,f=0,m=!1,v=!1,_=i(this).uploadinput({multiple:!0,onchange:function(e){n(e)},ondragover:e.ondragover,ondragleave:e.ondragleave});return{select:a,add:n,remove:s,start:l,stop:c,reset:r,configure:d,compatible:h}}}(e)}).call(t,i(1),i(2))},function(e,t,i){(function(t){var n=i(10).RightPanel,s=(i(7),i(112));CurrentHistoryView=i(109).CurrentHistoryView,_l=i(5);var o=n.extend({title:_l("History"),initialize:function(e){n.prototype.initialize.call(this,e),this.options=t.pick(e,"userIsAnonymous","allow_user_dataset_purge","galaxyRoot"),this.historyView=new CurrentHistoryView({className:CurrentHistoryView.prototype.className+" middle",purgeAllowed:e.allow_user_dataset_purge,linkTarget:"galaxy_main"})},$toggleButton:function(){return this.$(".footer > .panel-collapse")},render:function(){n.prototype.render.call(this),this.optionsMenu=s(this.$("#history-options-button"),{anonymous:this.options.userIsAnonymous,purgeAllowed:this.options.allow_user_dataset_purge,root:this.options.galaxyRoot}),this.$("> .header .buttons [title]").tooltip({placement:"bottom"}),this.historyView.setElement(this.$(".history-panel")),this.$el.attr("class","history-right-panel")},_templateHeader:function(e){var i=this.options.galaxyRoot+"history",n=this.options.galaxyRoot+"history/view_multiple";return['
"].join("")},_templateBody:function(e){return[''].join("")},_templateFooter:function(e){return['"+JSON.stringify(e.responseJSON)+"":": "+i),creator._showAlert(n,"alert-danger")},render:function(e,t){return this.$el.empty().html(p.templates.main()),this._renderHeader(e),this._renderMiddle(e),this._renderFooter(e),this._addPluginComponents(),this.trigger("rendered",this),this},_renderHeader:function(e,t){var i=this.$(".header").empty().html(p.templates.header()).find(".help-content").prepend(l(p.templates.helpContent()));return this._renderFilters(),i},_renderFilters:function(){return this.$(".forward-column .column-header input").val(this.filters[0]).add(this.$(".reverse-column .column-header input").val(this.filters[1]))},_renderMiddle:function(e,t){var i=this.$(".middle").empty().html(p.templates.middle());return this.unpairedPanelHidden?this.$(".unpaired-columns").hide():this.pairedPanelHidden&&this.$(".paired-columns").hide(),this._renderUnpaired(),this._renderPaired(),i},_renderUnpaired:function(e,t){var i,n,o=this,a=[],r=this._splitByFilters();return this.$(".forward-column .title").text([r[0].length,s("unpaired forward")].join(" ")),this.$(".forward-column .unpaired-info").text(this._renderUnpairedDisplayStr(this.unpaired.length-r[0].length)),this.$(".reverse-column .title").text([r[1].length,s("unpaired reverse")].join(" ")),this.$(".reverse-column .unpaired-info").text(this._renderUnpairedDisplayStr(this.unpaired.length-r[1].length)),this.$(".unpaired-columns .column-datasets").empty(),this.$(".autopair-link").toggle(0!==this.unpaired.length),0===this.unpaired.length?void this._renderUnpairedEmpty():(n=r[1].map(function(e,t){return void 0!==r[0][t]&&r[0][t]!==e&&a.push(o._renderPairButton()),o._renderUnpairedDataset(e)}),i=r[0].map(function(e){return o._renderUnpairedDataset(e)}),i.length||n.length?(this.$(".unpaired-columns .forward-column .column-datasets").append(i).add(this.$(".unpaired-columns .paired-column .column-datasets").append(a)).add(this.$(".unpaired-columns .reverse-column .column-datasets").append(n)), -void this._adjUnpairedOnScrollbar()):void this._renderUnpairedNotShown())},_renderUnpairedDisplayStr:function(e){return["(",e," ",s("filtered out"),")"].join("")},_renderUnpairedDataset:function(e){return l("").attr("id","dataset-"+e.id).addClass("dataset unpaired").attr("draggable",!0).addClass(e.selected?"selected":"").append(l("").addClass("dataset-name").text(e.name)).data("dataset",e)},_renderPairButton:function(){return l("").addClass("dataset unpaired").append(l("").addClass("dataset-name").text(s("Pair these datasets")))},_renderUnpairedEmpty:function(){var e=l('').text("("+s("no remaining unpaired datasets")+")");return this.$(".unpaired-columns .paired-column .column-datasets").empty().prepend(e),e},_renderUnpairedNotShown:function(){var e=l('').text("("+s("no datasets were found matching the current filters")+")");return this.$(".unpaired-columns .paired-column .column-datasets").empty().prepend(e),e},_adjUnpairedOnScrollbar:function(){var e=this.$(".unpaired-columns").last(),t=this.$(".unpaired-columns .reverse-column .dataset").first();if(t.length){var i=e.offset().left+e.outerWidth(),n=t.offset().left+t.outerWidth(),s=Math.floor(i)-Math.floor(n);this.$(".unpaired-columns .forward-column").css("margin-left",s>0?s:0)}},_renderPaired:function(e,t){if(this.$(".paired-column-title .title").text([this.paired.length,s("paired")].join(" ")),this.$(".unpair-all-link").toggle(0!==this.paired.length),0===this.paired.length)return void this._renderPairedEmpty();this.$(".remove-extensions-link").show(),this.$(".paired-columns .column-datasets").empty();var i=this;this.paired.forEach(function(e,t){var n=new u({pair:e});i.$(".paired-columns .column-datasets").append(n.render().$el).append(['"].join(""))})},_renderPairedEmpty:function(){var e=l('').text("("+s("no paired datasets yet")+")");return this.$(".paired-columns .column-datasets").empty().prepend(e),e},_renderFooter:function(e,t){var i=this.$(".footer").empty().html(p.templates.footer());return this.$(".remove-extensions").prop("checked",this.removeExtensions),"function"==typeof this.oncancel&&this.$(".cancel-create.btn").show(),i},_addPluginComponents:function(){this._chooseFiltersPopover(".choose-filters-link"),this.$(".help-content i").hoverhighlight(".collection-creator","rgba( 64, 255, 255, 1.0 )")},_chooseFiltersPopover:function(e){function t(e,t){return['"].join("")}var i=l(a.template(['
",s(["Collections of paired datasets are ordered lists of dataset pairs (often forward and reverse reads). ","These collections can be passed to tools and workflows in order to have analyses done on each member of ","the entire group. This interface allows you to create a collection, choose which datasets are paired, ","and re-order the final collection."].join("")),"
","",s(['Unpaired datasets are shown in the unpaired section ',"(hover over the underlined words to highlight below). ",'Paired datasets are shown in the paired section.',"
",s(["
",s(["To unpair individual dataset pairs, click the ",'unpair buttons ( ). ','Click the "Unpair all" link to unpair all pairs.'].join("")),"
","",s(['You can include or remove the file extensions (e.g. ".fastq") from your pair names by toggling the ','"Remove file extensions from pair names?" control.'].join("")),"
","",s(['Once your collection is complete, enter a name and ','click "Create list". ',"(Note: you do not have to pair all unpaired datasets to finish.)"].join("")),"
"].join(""))};var g=function(e,t){var i,n=r.Deferred();if(t=a.defaults(t||{},{datasets:e,oncancel:function(){Galaxy.modal.hide(),n.reject("cancelled")},oncreate:function(e,t){Galaxy.modal.hide(),n.resolve(t)}}),!window.Galaxy||!Galaxy.modal)throw new Error("Galaxy or Galaxy.modal not found");return i=new p(t),Galaxy.modal.show({title:"Create a collection of paired datasets",body:i.$el,width:"80%",height:"800px",closing_events:!0}),i.render(),window.creator=i,n};return{PairedCollectionCreator:p,pairedCollectionCreatorModal:g,createListOfPairsCollection:d}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(2),i(1),i(1))},function(e,t,i){var n,s;(function(o,a,r){n=[i(30),i(38),i(6),i(5)],s=function(e,t,i,n){"use strict";function s(e){var t=e.toJSON(),i=u(t,{creationFn:function(t,i){return t=[{name:"forward",src:"hda",id:t[0].id},{name:"reverse",src:"hda",id:t[1].id}],e.createHDCA(t,"paired",i)}});return i}var l="collections",c=o.View.extend(i.LoggableMixin).extend({_logNamespace:l,tagName:"li",className:"collection-element",initialize:function(e){this.element=e.element||{},this.identifier=e.identifier},render:function(){return this.$el.attr("data-element-id",this.element.id).html(this.template({identifier:this.identifier,element:this.element})),this},template:a.template(['<%- identifier %>','<%- element.name %>'].join("")),destroy:function(){this.off(),this.$el.remove()},toString:function(){return"DatasetCollectionElementView()"}}),d=e.ListCollectionCreator,h=d.extend({elementViewClass:c,collectionClass:t.HistoryPairDatasetCollection,className:"pair-collection-creator collection-creator flex-row-container",_mangleDuplicateNames:function(){},render:function(e,t){return 2===this.workingElements.length?d.prototype.render.call(this,e,t):this._renderInvalid(e,t)},_renderList:function(e,t){var i=this,n=r(""),s=i.$list();a.each(this.elementViews,function(e){e.destroy(),i.removeElementView(e)}),n.append(i._createForwardElementView().$el),n.append(i._createReverseElementView().$el),s.empty().append(n.children()),a.invoke(i.elementViews,"render")},_createForwardElementView:function(){return this._createElementView(this.workingElements[0],{identifier:"forward"})},_createReverseElementView:function(){return this._createElementView(this.workingElements[1],{identifier:"reverse"})},_createElementView:function(e,t){var i=new this.elementViewClass(a.extend(t,{element:e}));return this.elementViews.push(i),i},swap:function(){this.workingElements=[this.workingElements[1],this.workingElements[0]],this._renderList()},events:a.extend(a.clone(d.prototype.events),{"click .swap":"swap"}),templates:a.extend(a.clone(d.prototype.templates),{middle:a.template(['",n(["Pair collections are permanent collections containing two datasets: one forward and one reverse. ","Often these are forward and reverse reads. The pair collections can be passed to tools and ","workflows in order to have analyses done on both datasets. This interface allows ","you to create a pair, name it, and swap which is forward and which reverse."].join("")),"
","",n(['Once your collection is complete, enter a name and ','click "Create list".'].join("")),"
"].join("")),invalidInitial:a.template([''+this.progressive+"...
";this.modal.$(".modal-body").empty().append(e).css({"margin-top":"8px"})},dialog:function(e,n,s){function l(){var i=e.$("#copy-modal-title").val();if(!i)return void e.$(".invalid-title").show();var s="copy-all"===e.$('input[name="copy-what"]:checked').val();e.$("button").prop("disabled",!0),c._showAjaxIndicator(),n.copy(!0,i,s).done(function(e){d.resolve(e)}).fail(function(e,o,a){var r={name:i,copyAllDatasets:s};t.ajaxErrorModal(n,e,r,c.errorMessage),d.rejectWith(d,arguments)}).done(function(){f&&e.hide()})}s=s||{};var c=this,d=a.Deferred(),h=s.nameFn||this.defaultName,u=h({name:n.get("name")}),p=s.allDatasets?"copy-all":"copy-non-deleted",g=!!o.isUndefined(s.allowAll)||s.allowAll,f=!!o.isUndefined(s.autoClose)||s.autoClose;this.modal=e;var m=s.closing_callback;return e.show(o.extend(s,{title:this.title({name:n.get("name")}),body:r(c._template({name:u,isAnon:Galaxy.user.isAnonymous(),allowAll:g,copyWhat:p,activeLabel:this.activeLabel,allLabel:this.allLabel,anonWarning:this.anonWarning})),buttons:o.object([[i("Cancel"),function(){e.hide()}],[this.submitLabel,l]]),height:"auto",closing_events:!0,closing_callback:function(e){e&&d.reject({cancelled:!0}),m&&m(e)}})),e.$("#copy-modal-title").focus().select(),e.$("#copy-modal-title").on("keydown",function(e){13===e.keyCode&&(e.preventDefault(),l())}),d}},s=o.extend({},n,{defaultName:o.template("imported: <%- name %>"),title:o.template(i("Importing history")+' "<%- name %>"'),submitLabel:i("Import"),errorMessage:i("History could not be imported."),progressive:i("Importing history"),activeLabel:i("Import only the active, non-deleted datasets"),allLabel:i("Import all datasets including deleted ones"),anonWarning:i("As an anonymous user, unless you login or register, you will lose your current history ")+i("after importing this history. ")}),l=function(t,i){i=i||{};var o=window.parent.Galaxy.modal||new e.View({});return i.useImport?s.dialog(o,t,i):n.dialog(o,t,i)};return l}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2),i(1),i(1))},function(e,t,i){var n,s;(function(o,a){n=[i(69),i(71),i(6),i(5)],s=function(e,t,i,n){"use strict";var s=e.DatasetListItemEdit,r=s.extend({className:s.prototype.className+" history-content",_fetchModelDetails:function(){var e=this;return e.model.inReadyState()&&!e.model.hasDetails()?e.model.fetch({silent:!0}):e.model.has("rerunnable")?o.when():e.model.fetch({silent:!0,data:{keys:["rerunnable","creating_job"].join(",")}})},events:a.extend(a.clone(s.prototype.events),{"click .unhide-link":function(e){return this.model.unhide(),!1}}),toString:function(){var e=this.model?this.model+"":"(no model)";return"HDAListItemEdit("+e+")"}});return r.prototype.templates=function(){var e=a.extend({},s.prototype.templates.warnings,{hidden:i.wrapTemplate(["<% if( !dataset.visible ){ %>",'| Description | Name | Size | Settings | Status |
|---|
| Name | Size | Type | Genome | Settings | Status |
|---|
| Name | Size | Created |
|---|
h)return 1}return 0}return e}.apply(t,n),!(void 0!==s&&(e.exports=s))},function(e,t,i){(function(e,t){!function(i){e.event.props.push("dataTransfer"),i.uploadpost=function(t){var n=i.extend({},{data:{},success:function(){},error:function(){},progress:function(){},url:null,maxfilesize:2048,error_filesize:"File exceeds 2GB. Please use a FTP client.",error_default:"Please make sure the file is available.",error_server:"Upload request failed.",error_login:"Uploads require you to log in."},t),s=n.data;if(s.error_message)return void n.error(s.error_message);var o=new FormData;for(var a in s.payload)o.append(a,s.payload[a]);var r=0;for(var a in s.files){var l=s.files[a];o.append(l.name,l.file,l.file.name),r+=l.file.size}return r>1048576*n.maxfilesize?void n.error(n.error_filesize):(xhr=new XMLHttpRequest,xhr.open("POST",n.url,!0),xhr.setRequestHeader("Accept","application/json"),xhr.setRequestHeader("Cache-Control","no-cache"),xhr.setRequestHeader("X-Requested-With","XMLHttpRequest"),xhr.onreadystatechange=function(){if(xhr.readyState==xhr.DONE){var t=null;if(xhr.responseText)try{t=e.parseJSON(xhr.responseText)}catch(i){t=xhr.responseText}if(xhr.status<200||xhr.status>299){var s=xhr.statusText;403==xhr.status?s=n.error_login:0==xhr.status?s=n.error_server:s||(s=n.error_default),n.error(s+" ("+xhr.status+")")}else n.success(t)}},xhr.upload.addEventListener("progress",function(e){e.lengthComputable&&n.progress(Math.round(100*e.loaded/e.total))},!1),Galaxy.emit.debug("uploadbox::uploadpost()","Posting following data.",n),void xhr.send(o))},i.fn.uploadinput=function(e){var t=this,n=i.extend({},{ondragover:function(){},ondragleave:function(){},onchange:function(){},multiple:!1},e),s=i('");return t.append(s.change(function(e){n.onchange(e.target.files),i(this).val("")})),t.on("drop",function(e){n.ondragleave(e),e.dataTransfer&&(n.onchange(e.dataTransfer.files),e.preventDefault())}),t.on("dragover",function(e){e.preventDefault(),n.ondragover(e)}),t.on("dragleave",function(e){e.stopPropagation(),n.ondragleave(e)}),{dialog:function(){s.trigger("click")}}},i.fn.uploadbox=function(e){function n(e){if(e&&e.length&&!m){var i=g;return t.each(e,function(e,i){"new"!==e.mode&&t.filter(p,function(t){return t.name===e.name&&t.size===e.size}).length&&(e.duplicate=!0)}),t.each(e,function(e){if(!e.duplicate){var t=String(g++);p[t]=e,u.announce(t,p[t]),f++}}),i}}function s(e){p[e]&&(delete p[e],f--)}function o(){if(0==f||v)return v=!1,m=!1,void u.complete();m=!0;var e=-1;for(var t in p){e=t;break}p[e];s(e),i.uploadpost({url:u.url,data:u.initialize(e),success:function(t){u.success(e,t),o()},error:function(t){u.error(e,t),o()},progress:function(t){u.progress(e,t)}})}function a(){_.dialog()}function r(e){for(e in p)s(e)}function l(){m||(m=!0,o())}function c(){v=!0}function d(e){return u=i.extend({},u,e)}function h(){return window.File&&window.FormData&&window.XMLHttpRequest&&window.FileList}var u=i.extend({},{dragover:function(){},dragleave:function(){},announce:function(e){},initialize:function(e){},progress:function(e,t){},success:function(e,t){},error:function(e,t){alert(t)},complete:function(){}},e),p={},g=0,f=0,m=!1,v=!1,_=i(this).uploadinput({multiple:!0,onchange:function(e){n(e)},ondragover:e.ondragover,ondragleave:e.ondragleave});return{select:a,add:n,remove:s,start:l,stop:c,reset:r,configure:d,compatible:h}}}(e)}).call(t,i(1),i(2))},function(e,t,i){(function(t){var n=i(10).RightPanel,s=(i(7),i(116));CurrentHistoryView=i(113).CurrentHistoryView,_l=i(5);var o=n.extend({title:_l("History"),initialize:function(e){n.prototype.initialize.call(this,e),this.options=t.pick(e,"userIsAnonymous","allow_user_dataset_purge","galaxyRoot"),this.historyView=new CurrentHistoryView({className:CurrentHistoryView.prototype.className+" middle",purgeAllowed:e.allow_user_dataset_purge,linkTarget:"galaxy_main"})},$toggleButton:function(){return this.$(".footer > .panel-collapse")},render:function(){n.prototype.render.call(this),this.optionsMenu=s(this.$("#history-options-button"),{anonymous:this.options.userIsAnonymous,purgeAllowed:this.options.allow_user_dataset_purge,root:this.options.galaxyRoot}),this.$("> .header .buttons [title]").tooltip({placement:"bottom"}),this.historyView.setElement(this.$(".history-panel")),this.$el.attr("class","history-right-panel")},_templateHeader:function(e){var i=this.options.galaxyRoot+"history",n=this.options.galaxyRoot+"history/view_multiple";return['
"].join("")},_templateBody:function(e){return[''].join("")},_templateFooter:function(e){return['"+JSON.stringify(e.responseJSON)+"":": "+i),creator._showAlert(n,"alert-danger")},render:function(e,t){return this.$el.empty().html(p.templates.main()),this._renderHeader(e),this._renderMiddle(e),this._renderFooter(e),this._addPluginComponents(),this.trigger("rendered",this),this},_renderHeader:function(e,t){var i=this.$(".header").empty().html(p.templates.header()).find(".help-content").prepend(l(p.templates.helpContent()));return this._renderFilters(),i},_renderFilters:function(){return this.$(".forward-column .column-header input").val(this.filters[0]).add(this.$(".reverse-column .column-header input").val(this.filters[1]))},_renderMiddle:function(e,t){var i=this.$(".middle").empty().html(p.templates.middle());return this.unpairedPanelHidden?this.$(".unpaired-columns").hide():this.pairedPanelHidden&&this.$(".paired-columns").hide(),this._renderUnpaired(),this._renderPaired(),i},_renderUnpaired:function(e,t){var i,n,o=this,a=[],r=this._splitByFilters();return this.$(".forward-column .title").text([r[0].length,s("unpaired forward")].join(" ")),this.$(".forward-column .unpaired-info").text(this._renderUnpairedDisplayStr(this.unpaired.length-r[0].length)),this.$(".reverse-column .title").text([r[1].length,s("unpaired reverse")].join(" ")),this.$(".reverse-column .unpaired-info").text(this._renderUnpairedDisplayStr(this.unpaired.length-r[1].length)),this.$(".unpaired-columns .column-datasets").empty(),this.$(".autopair-link").toggle(0!==this.unpaired.length),0===this.unpaired.length?void this._renderUnpairedEmpty():(n=r[1].map(function(e,t){return void 0!==r[0][t]&&r[0][t]!==e&&a.push(o._renderPairButton()),o._renderUnpairedDataset(e)}),i=r[0].map(function(e){return o._renderUnpairedDataset(e)}),i.length||n.length?(this.$(".unpaired-columns .forward-column .column-datasets").append(i).add(this.$(".unpaired-columns .paired-column .column-datasets").append(a)).add(this.$(".unpaired-columns .reverse-column .column-datasets").append(n)), +void this._adjUnpairedOnScrollbar()):void this._renderUnpairedNotShown())},_renderUnpairedDisplayStr:function(e){return["(",e," ",s("filtered out"),")"].join("")},_renderUnpairedDataset:function(e){return l("").attr("id","dataset-"+e.id).addClass("dataset unpaired").attr("draggable",!0).addClass(e.selected?"selected":"").append(l("").addClass("dataset-name").text(e.name)).data("dataset",e)},_renderPairButton:function(){return l("").addClass("dataset unpaired").append(l("").addClass("dataset-name").text(s("Pair these datasets")))},_renderUnpairedEmpty:function(){var e=l('').text("("+s("no remaining unpaired datasets")+")");return this.$(".unpaired-columns .paired-column .column-datasets").empty().prepend(e),e},_renderUnpairedNotShown:function(){var e=l('').text("("+s("no datasets were found matching the current filters")+")");return this.$(".unpaired-columns .paired-column .column-datasets").empty().prepend(e),e},_adjUnpairedOnScrollbar:function(){var e=this.$(".unpaired-columns").last(),t=this.$(".unpaired-columns .reverse-column .dataset").first();if(t.length){var i=e.offset().left+e.outerWidth(),n=t.offset().left+t.outerWidth(),s=Math.floor(i)-Math.floor(n);this.$(".unpaired-columns .forward-column").css("margin-left",s>0?s:0)}},_renderPaired:function(e,t){if(this.$(".paired-column-title .title").text([this.paired.length,s("paired")].join(" ")),this.$(".unpair-all-link").toggle(0!==this.paired.length),0===this.paired.length)return void this._renderPairedEmpty();this.$(".remove-extensions-link").show(),this.$(".paired-columns .column-datasets").empty();var i=this;this.paired.forEach(function(e,t){var n=new u({pair:e});i.$(".paired-columns .column-datasets").append(n.render().$el).append(['"].join(""))})},_renderPairedEmpty:function(){var e=l('').text("("+s("no paired datasets yet")+")");return this.$(".paired-columns .column-datasets").empty().prepend(e),e},_renderFooter:function(e,t){var i=this.$(".footer").empty().html(p.templates.footer());return this.$(".remove-extensions").prop("checked",this.removeExtensions),"function"==typeof this.oncancel&&this.$(".cancel-create.btn").show(),i},_addPluginComponents:function(){this._chooseFiltersPopover(".choose-filters-link"),this.$(".help-content i").hoverhighlight(".collection-creator","rgba( 64, 255, 255, 1.0 )")},_chooseFiltersPopover:function(e){function t(e,t){return['"].join("")}var i=l(a.template(['
",s(["Collections of paired datasets are ordered lists of dataset pairs (often forward and reverse reads). ","These collections can be passed to tools and workflows in order to have analyses done on each member of ","the entire group. This interface allows you to create a collection, choose which datasets are paired, ","and re-order the final collection."].join("")),"
","",s(['Unpaired datasets are shown in the unpaired section ',"(hover over the underlined words to highlight below). ",'Paired datasets are shown in the paired section.',"
",s(["
",s(["To unpair individual dataset pairs, click the ",'unpair buttons ( ). ','Click the "Unpair all" link to unpair all pairs.'].join("")),"
","",s(['You can include or remove the file extensions (e.g. ".fastq") from your pair names by toggling the ','"Remove file extensions from pair names?" control.'].join("")),"
","",s(['Once your collection is complete, enter a name and ','click "Create list". ',"(Note: you do not have to pair all unpaired datasets to finish.)"].join("")),"
"].join(""))};var g=function(e,t){var i,n=r.Deferred();if(t=a.defaults(t||{},{datasets:e,oncancel:function(){Galaxy.modal.hide(),n.reject("cancelled")},oncreate:function(e,t){Galaxy.modal.hide(),n.resolve(t)}}),!window.Galaxy||!Galaxy.modal)throw new Error("Galaxy or Galaxy.modal not found");return i=new p(t),Galaxy.modal.show({title:"Create a collection of paired datasets",body:i.$el,width:"80%",height:"800px",closing_events:!0}),i.render(),window.creator=i,n};return{PairedCollectionCreator:p,pairedCollectionCreatorModal:g,createListOfPairsCollection:d}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(2),i(1),i(1))},function(e,t,i){var n,s;(function(o,a,r){n=[i(31),i(39),i(6),i(5)],s=function(e,t,i,n){"use strict";function s(e){var t=e.toJSON(),i=u(t,{creationFn:function(t,i){return t=[{name:"forward",src:"hda",id:t[0].id},{name:"reverse",src:"hda",id:t[1].id}],e.createHDCA(t,"paired",i)}});return i}var l="collections",c=o.View.extend(i.LoggableMixin).extend({_logNamespace:l,tagName:"li",className:"collection-element",initialize:function(e){this.element=e.element||{},this.identifier=e.identifier},render:function(){return this.$el.attr("data-element-id",this.element.id).html(this.template({identifier:this.identifier,element:this.element})),this},template:a.template(['<%- identifier %>','<%- element.name %>'].join("")),destroy:function(){this.off(),this.$el.remove()},toString:function(){return"DatasetCollectionElementView()"}}),d=e.ListCollectionCreator,h=d.extend({elementViewClass:c,collectionClass:t.HistoryPairDatasetCollection,className:"pair-collection-creator collection-creator flex-row-container",_mangleDuplicateNames:function(){},render:function(e,t){return 2===this.workingElements.length?d.prototype.render.call(this,e,t):this._renderInvalid(e,t)},_renderList:function(e,t){var i=this,n=r(""),s=i.$list();a.each(this.elementViews,function(e){e.destroy(),i.removeElementView(e)}),n.append(i._createForwardElementView().$el),n.append(i._createReverseElementView().$el),s.empty().append(n.children()),a.invoke(i.elementViews,"render")},_createForwardElementView:function(){return this._createElementView(this.workingElements[0],{identifier:"forward"})},_createReverseElementView:function(){return this._createElementView(this.workingElements[1],{identifier:"reverse"})},_createElementView:function(e,t){var i=new this.elementViewClass(a.extend(t,{element:e}));return this.elementViews.push(i),i},swap:function(){this.workingElements=[this.workingElements[1],this.workingElements[0]],this._renderList()},events:a.extend(a.clone(d.prototype.events),{"click .swap":"swap"}),templates:a.extend(a.clone(d.prototype.templates),{middle:a.template(['",n(["Pair collections are permanent collections containing two datasets: one forward and one reverse. ","Often these are forward and reverse reads. The pair collections can be passed to tools and ","workflows in order to have analyses done on both datasets. This interface allows ","you to create a pair, name it, and swap which is forward and which reverse."].join("")),"
","",n(['Once your collection is complete, enter a name and ','click "Create list".'].join("")),"
"].join("")),invalidInitial:a.template([''+this.progressive+"...
";this.modal.$(".modal-body").empty().append(e).css({"margin-top":"8px"})},dialog:function(e,n,s){function l(){var i=e.$("#copy-modal-title").val();if(!i)return void e.$(".invalid-title").show();var s="copy-all"===e.$('input[name="copy-what"]:checked').val();e.$("button").prop("disabled",!0),c._showAjaxIndicator(),n.copy(!0,i,s).done(function(e){d.resolve(e)}).fail(function(e,o,a){var r={name:i,copyAllDatasets:s};t.ajaxErrorModal(n,e,r,c.errorMessage),d.rejectWith(d,arguments)}).done(function(){f&&e.hide()})}s=s||{};var c=this,d=a.Deferred(),h=s.nameFn||this.defaultName,u=h({name:n.get("name")}),p=s.allDatasets?"copy-all":"copy-non-deleted",g=!!o.isUndefined(s.allowAll)||s.allowAll,f=!!o.isUndefined(s.autoClose)||s.autoClose;this.modal=e;var m=s.closing_callback;return e.show(o.extend(s,{title:this.title({name:n.get("name")}),body:r(c._template({name:u,isAnon:Galaxy.user.isAnonymous(),allowAll:g,copyWhat:p,activeLabel:this.activeLabel,allLabel:this.allLabel,anonWarning:this.anonWarning})),buttons:o.object([[i("Cancel"),function(){e.hide()}],[this.submitLabel,l]]),height:"auto",closing_events:!0,closing_callback:function(e){e&&d.reject({cancelled:!0}),m&&m(e)}})),e.$("#copy-modal-title").focus().select(),e.$("#copy-modal-title").on("keydown",function(e){13===e.keyCode&&(e.preventDefault(),l())}),d}},s=o.extend({},n,{defaultName:o.template("imported: <%- name %>"),title:o.template(i("Importing history")+' "<%- name %>"'),submitLabel:i("Import"),errorMessage:i("History could not be imported."),progressive:i("Importing history"),activeLabel:i("Import only the active, non-deleted datasets"),allLabel:i("Import all datasets including deleted ones"),anonWarning:i("As an anonymous user, unless you login or register, you will lose your current history ")+i("after importing this history. ")}),l=function(t,i){i=i||{};var o=window.parent.Galaxy.modal||new e.View({});return i.useImport?s.dialog(o,t,i):n.dialog(o,t,i)};return l}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2),i(1),i(1))},function(e,t,i){var n,s;(function(o,a){n=[i(69),i(71),i(6),i(5)],s=function(e,t,i,n){"use strict";var s=e.DatasetListItemEdit,r=s.extend({className:s.prototype.className+" history-content",_fetchModelDetails:function(){var e=this;return e.model.inReadyState()&&!e.model.hasDetails()?e.model.fetch({silent:!0}):e.model.has("rerunnable")?o.when():e.model.fetch({silent:!0,data:{keys:["rerunnable","creating_job"].join(",")}})},events:a.extend(a.clone(s.prototype.events),{"click .unhide-link":function(e){return this.model.unhide(),!1}}),toString:function(){var e=this.model?this.model+"":"(no model)";return"HDAListItemEdit("+e+")"}});return r.prototype.templates=function(){var e=a.extend({},s.prototype.templates.warnings,{hidden:i.wrapTemplate(["<% if( !dataset.visible ){ %>",'| Description | Name | Size | Settings | Status |
|---|
| Name | Size | Type | Genome | Settings | Status |
|---|
\" + this.formattedReference() + \"
\" );\n\t return this;\n\t },\n\t formattedReference: function() {\n\t var model = this.model;\n\t var entryType = model.entryType();\n\t var fields = model.fields();\n\t\n\t var ref = \"\";\n\t // Code inspired by...\n\t // https://github.com/vkaravir/bib-publication-list/blob/master/src/bib-publication-list.js\n\t var authorsAndYear = this._asSentence( (fields.author ? fields.author : \"\") + (fields.year ? (\" (\" + fields.year + \")\") : \"\") ) + \" \";\n\t var title = fields.title || \"\";\n\t var pages = fields.pages ? (\"pp. \" + fields.pages) : \"\";\n\t var address = fields.address;\n\t if( entryType == \"article\" ) {\n\t var volume = (fields.volume ? fields.volume : \"\") +\n\t (fields.number ? ( \" (\" + fields.number + \")\" ) : \"\") +\n\t (pages ? \", \" + pages : \"\");\n\t ref = authorsAndYear + this._asSentence(title) +\n\t (fields.journal ? (\"In \" + fields.journal + \", \") : \"\") +\n\t this._asSentence(volume) + \n\t this._asSentence(fields.address) +\n\t \"<\\/em>\";\n\t } else if( entryType == \"inproceedings\" || entryType == \"proceedings\" ) {\n\t ref = authorsAndYear + \n\t this._asSentence(title) + \n\t (fields.booktitle ? (\"In \" + fields.booktitle + \", \") : \"\") +\n\t (pages ? pages : \"\") +\n\t (address ? \", \" + address : \"\") + \n\t \".<\\/em>\";\n\t } else if( entryType == \"mastersthesis\" || entryType == \"phdthesis\" ) {\n\t ref = authorsAndYear + this._asSentence(title) +\n\t (fields.howpublished ? fields.howpublished + \". \" : \"\") +\n\t (fields.note ? fields.note + \".\" : \"\");\n\t } else if( entryType == \"techreport\" ) {\n\t ref = authorsAndYear + this._asSentence(title) +\n\t this._asSentence(fields.institution) +\n\t this._asSentence(fields.number) +\n\t this._asSentence(fields.type);\n\t } else if( entryType == \"book\" || entryType == \"inbook\" || entryType == \"incollection\" ) {\n\t ref = authorsAndYear + \" \" + this._formatBookInfo(fields);\n\t } else {\n\t ref = authorsAndYear + \" \" + this._asSentence(title) +\n\t this._asSentence(fields.howpublished) +\n\t this._asSentence(fields.note);\n\t }\n\t var doiUrl = \"\";\n\t if( fields.doi ) {\n\t doiUrl = 'http://dx.doi.org/' + fields.doi;\n\t ref += '[doi:' + fields.doi + \"]\";\n\t }\n\t var url = fields.url || doiUrl;\n\t if( url ) {\n\t ref += '[Link]';\n\t }\n\t return ref;\n\t },\n\t _formatBookInfo: function(fields) {\n\t var info = \"\";\n\t if( fields.chapter ) {\n\t info += fields.chapter + \" in \";\n\t }\n\t if( fields.title ) {\n\t info += \"\" + fields.title + \"<\\/em>\";\n\t }\n\t if( fields.editor ) {\n\t info += \", Edited by \" + fields.editor + \", \";\n\t }\n\t if( fields.publisher) {\n\t info += \", \" + fields.publisher;\n\t }\n\t if( fields.pages ) {\n\t info += \", pp. \" + fields.pages + \"\";\n\t }\n\t if( fields.series ) {\n\t info += \", \" + fields.series + \"<\\/em>\";\n\t }\n\t if( fields.volume ) {\n\t info += \", Vol.\" + fields.volume;\n\t }\n\t if( fields.issn ) {\n\t info += \", ISBN: \" + fields.issn;\n\t }\n\t return info + \".\";\n\t },\n\t _asSentence: function(str) {\n\t return (str && str.trim()) ? str + \". \" : \"\";\n\t }\n\t});\n\t\n\tvar CitationListView = Backbone.View.extend({\n\t el: '#citations',\n\t /**\n\t * Set up view.\n\t */\n\t initialize: function() {\n\t this.listenTo( this.collection, 'add', this.renderCitation );\n\t },\n\t\n\t events: {\n\t 'click .citations-to-bibtex': 'showBibtex',\n\t 'click .citations-to-formatted': 'showFormatted'\n\t },\n\t\n\t renderCitation: function( citation ) {\n\t var citationView = new CitationView( { model: citation } );\n\t this.$(\".citations-formatted\").append( citationView.render().el );\n\t var rawTextarea = this.$(\".citations-bibtex-text\");\n\t rawTextarea.val( rawTextarea.val() + \"\\n\\r\" + citation.attributes.content );\n\t },\n\t\n\t render: function() {\n\t this.$el.html(this.citationsElement());\n\t this.collection.each(function( item ){\n\t this.renderCitation( item );\n\t }, this);\n\t this.showFormatted();\n\t },\n\t\n\t showBibtex: function() {\n\t this.$(\".citations-to-formatted\").show();\n\t this.$(\".citations-to-bibtex\").hide();\n\t this.$(\".citations-bibtex\").show();\n\t this.$(\".citations-formatted\").hide();\n\t this.$(\".citations-bibtex-text\").select();\n\t },\n\t\n\t showFormatted: function() {\n\t this.$(\".citations-to-formatted\").hide();\n\t this.$(\".citations-to-bibtex\").show();\n\t this.$(\".citations-bibtex\").hide();\n\t this.$(\".citations-formatted\").show();\n\t },\n\t\n\t partialWarningElement: function() {\n\t if( this.collection.partial ) {\n\t return [\n\t '' + JSON.stringify( xhr.responseJSON ) + '';\n\t } else {\n\t content += ': ' + message;\n\t }\n\t }\n\t creator._showAlert( content, 'alert-danger' );\n\t },\n\t\n\t events : {\n\t // header\n\t 'click .more-help' : '_clickMoreHelp',\n\t 'click .less-help' : '_clickLessHelp',\n\t 'click .main-help' : '_toggleHelp',\n\t 'click .header .alert button' : '_hideAlert',\n\t\n\t 'click .reset' : 'reset',\n\t 'click .clear-selected' : 'clearSelectedElements',\n\t\n\t // elements - selection\n\t 'click .collection-elements' : 'clearSelectedElements',\n\t\n\t // elements - drop target\n\t // 'dragenter .collection-elements': '_dragenterElements',\n\t // 'dragleave .collection-elements': '_dragleaveElements',\n\t 'dragover .collection-elements' : '_dragoverElements',\n\t 'drop .collection-elements' : '_dropElements',\n\t\n\t // these bubble up from the elements as custom events\n\t 'collection-element.dragstart .collection-elements' : '_elementDragstart',\n\t 'collection-element.dragend .collection-elements' : '_elementDragend',\n\t\n\t // footer\n\t 'change .collection-name' : '_changeName',\n\t 'keydown .collection-name' : '_nameCheckForEnter',\n\t 'click .cancel-create' : function( ev ){\n\t if( typeof this.oncancel === 'function' ){\n\t this.oncancel.call( this );\n\t }\n\t },\n\t 'click .create-collection' : '_clickCreate'//,\n\t },\n\t\n\t // ........................................................................ header\n\t /** expand help */\n\t _clickMoreHelp : function( ev ){\n\t ev.stopPropagation();\n\t this.$( '.main-help' ).addClass( 'expanded' );\n\t this.$( '.more-help' ).hide();\n\t },\n\t /** collapse help */\n\t _clickLessHelp : function( ev ){\n\t ev.stopPropagation();\n\t this.$( '.main-help' ).removeClass( 'expanded' );\n\t this.$( '.more-help' ).show();\n\t },\n\t /** toggle help */\n\t _toggleHelp : function( ev ){\n\t ev.stopPropagation();\n\t this.$( '.main-help' ).toggleClass( 'expanded' );\n\t this.$( '.more-help' ).toggle();\n\t },\n\t\n\t /** show an alert on the top of the interface containing message (alertClass is bootstrap's alert-*) */\n\t _showAlert : function( message, alertClass ){\n\t alertClass = alertClass || 'alert-danger';\n\t this.$( '.main-help' ).hide();\n\t this.$( '.header .alert' )\n\t .attr( 'class', 'alert alert-dismissable' ).addClass( alertClass ).show()\n\t .find( '.alert-message' ).html( message );\n\t },\n\t /** hide the alerts at the top */\n\t _hideAlert : function( message ){\n\t this.$( '.main-help' ).show();\n\t this.$( '.header .alert' ).hide();\n\t },\n\t\n\t // ........................................................................ elements\n\t /** reset all data to the initial state */\n\t reset : function(){\n\t this._instanceSetUp();\n\t this._elementsSetUp();\n\t this.render();\n\t },\n\t\n\t /** deselect all elements */\n\t clearSelectedElements : function( ev ){\n\t this.$( '.collection-elements .collection-element' ).removeClass( 'selected' );\n\t this.$( '.collection-elements-controls > .clear-selected' ).hide();\n\t },\n\t\n\t //_dragenterElements : function( ev ){\n\t // //this.debug( '_dragenterElements:', ev );\n\t //},\n\t//TODO: if selected are dragged out of the list area - remove the placeholder - cuz it won't work anyway\n\t // _dragleaveElements : function( ev ){\n\t // //this.debug( '_dragleaveElements:', ev );\n\t // },\n\t\n\t /** track the mouse drag over the list adding a placeholder to show where the drop would occur */\n\t _dragoverElements : function( ev ){\n\t //this.debug( '_dragoverElements:', ev );\n\t ev.preventDefault();\n\t\n\t var $list = this.$list();\n\t this._checkForAutoscroll( $list, ev.originalEvent.clientY );\n\t var $nearest = this._getNearestElement( ev.originalEvent.clientY );\n\t\n\t //TODO: no need to re-create - move instead\n\t this.$( '.element-drop-placeholder' ).remove();\n\t var $placeholder = $( '' );\n\t if( !$nearest.length ){\n\t $list.append( $placeholder );\n\t } else {\n\t $nearest.before( $placeholder );\n\t }\n\t },\n\t\n\t /** If the mouse is near enough to the list's top or bottom, scroll the list */\n\t _checkForAutoscroll : function( $element, y ){\n\t var AUTOSCROLL_SPEED = 2,\n\t offset = $element.offset(),\n\t scrollTop = $element.scrollTop(),\n\t upperDist = y - offset.top,\n\t lowerDist = ( offset.top + $element.outerHeight() ) - y;\n\t if( upperDist >= 0 && upperDist < this.autoscrollDist ){\n\t $element.scrollTop( scrollTop - AUTOSCROLL_SPEED );\n\t } else if( lowerDist >= 0 && lowerDist < this.autoscrollDist ){\n\t $element.scrollTop( scrollTop + AUTOSCROLL_SPEED );\n\t }\n\t },\n\t\n\t /** get the nearest element based on the mouse's Y coordinate.\n\t * If the y is at the end of the list, return an empty jQuery object.\n\t */\n\t _getNearestElement : function( y ){\n\t var WIGGLE = 4,\n\t lis = this.$( '.collection-elements li.collection-element' ).toArray();\n\t for( var i=0; i
', _l([\n\t 'Collections of datasets are permanent, ordered lists of datasets that can be passed to tools and ',\n\t 'workflows in order to have analyses done on each member of the entire group. This interface allows ',\n\t 'you to create a collection and re-order the final collection.'\n\t ].join( '' )), '
',\n\t '', _l([\n\t 'Once your collection is complete, enter a name and ',\n\t 'click \"Create list\".'\n\t ].join( '' )), '
'\n\t ].join('')),\n\t\n\t /** shown in list when all elements are discarded */\n\t invalidElements : _.template([\n\t _l( 'The following selections could not be included due to problems:' ),\n\t '<%= dataset.peek %>',\n\t '<% } %>',\n\t '<% } %>',\n\t '
| ' +\n\t ' | Name | ' +\n\t 'Size | ' +\n\t 'Created | ' +\n\t '
|---|
' + JSON.stringify( xhr.responseJSON ) + '';\n\t } else {\n\t content += ': ' + message;\n\t }\n\t }\n\t creator._showAlert( content, 'alert-danger' );\n\t },\n\t\n\t // ------------------------------------------------------------------------ rendering\n\t /** render the entire interface */\n\t render : function( speed, callback ){\n\t //this.debug( '-- _render' );\n\t //this.$el.empty().html( PairedCollectionCreator.templates.main() );\n\t this.$el.empty().html( PairedCollectionCreator.templates.main() );\n\t this._renderHeader( speed );\n\t this._renderMiddle( speed );\n\t this._renderFooter( speed );\n\t this._addPluginComponents();\n\t this.trigger( 'rendered', this );\n\t return this;\n\t },\n\t\n\t /** render the header section */\n\t _renderHeader : function( speed, callback ){\n\t //this.debug( '-- _renderHeader' );\n\t var $header = this.$( '.header' ).empty().html( PairedCollectionCreator.templates.header() )\n\t .find( '.help-content' ).prepend( $( PairedCollectionCreator.templates.helpContent() ) );\n\t\n\t this._renderFilters();\n\t return $header;\n\t },\n\t /** fill the filter inputs with the filter values */\n\t _renderFilters : function(){\n\t return this.$( '.forward-column .column-header input' ).val( this.filters[0] )\n\t .add( this.$( '.reverse-column .column-header input' ).val( this.filters[1] ) );\n\t },\n\t\n\t /** render the middle including unpaired and paired sections (which may be hidden) */\n\t _renderMiddle : function( speed, callback ){\n\t var $middle = this.$( '.middle' ).empty().html( PairedCollectionCreator.templates.middle() );\n\t\n\t // (re-) hide the un/paired panels based on instance vars\n\t if( this.unpairedPanelHidden ){\n\t this.$( '.unpaired-columns' ).hide();\n\t } else if( this.pairedPanelHidden ){\n\t this.$( '.paired-columns' ).hide();\n\t }\n\t\n\t this._renderUnpaired();\n\t this._renderPaired();\n\t return $middle;\n\t },\n\t /** render the unpaired section, showing datasets accrd. to filters, update the unpaired counts */\n\t _renderUnpaired : function( speed, callback ){\n\t //this.debug( '-- _renderUnpaired' );\n\t var creator = this,\n\t $fwd, $rev, $prd = [],\n\t split = this._splitByFilters();\n\t // update unpaired counts\n\t this.$( '.forward-column .title' )\n\t .text([ split[0].length, _l( 'unpaired forward' ) ].join( ' ' ));\n\t this.$( '.forward-column .unpaired-info' )\n\t .text( this._renderUnpairedDisplayStr( this.unpaired.length - split[0].length ) );\n\t this.$( '.reverse-column .title' )\n\t .text([ split[1].length, _l( 'unpaired reverse' ) ].join( ' ' ));\n\t this.$( '.reverse-column .unpaired-info' )\n\t .text( this._renderUnpairedDisplayStr( this.unpaired.length - split[1].length ) );\n\t\n\t this.$( '.unpaired-columns .column-datasets' ).empty();\n\t\n\t // show/hide the auto pair button if any unpaired are left\n\t this.$( '.autopair-link' ).toggle( this.unpaired.length !== 0 );\n\t if( this.unpaired.length === 0 ){\n\t this._renderUnpairedEmpty();\n\t return;\n\t }\n\t\n\t // create the dataset dom arrays\n\t $rev = split[1].map( function( dataset, i ){\n\t // if there'll be a fwd dataset across the way, add a button to pair the row\n\t if( ( split[0][ i ] !== undefined )\n\t && ( split[0][ i ] !== dataset ) ){\n\t $prd.push( creator._renderPairButton() );\n\t }\n\t return creator._renderUnpairedDataset( dataset );\n\t });\n\t $fwd = split[0].map( function( dataset ){\n\t return creator._renderUnpairedDataset( dataset );\n\t });\n\t\n\t if( !$fwd.length && !$rev.length ){\n\t this._renderUnpairedNotShown();\n\t return;\n\t }\n\t // add to appropo cols\n\t //TODO: not the best way to render - consider rendering the entire unpaired-columns section in a fragment\n\t // and swapping out that\n\t this.$( '.unpaired-columns .forward-column .column-datasets' ).append( $fwd )\n\t .add( this.$( '.unpaired-columns .paired-column .column-datasets' ).append( $prd ) )\n\t .add( this.$( '.unpaired-columns .reverse-column .column-datasets' ).append( $rev ) );\n\t this._adjUnpairedOnScrollbar();\n\t },\n\t /** return a string to display the count of filtered out datasets */\n\t _renderUnpairedDisplayStr : function( numFiltered ){\n\t return [ '(', numFiltered, ' ', _l( 'filtered out' ), ')' ].join('');\n\t },\n\t /** return an unattached jQuery DOM element to represent an unpaired dataset */\n\t _renderUnpairedDataset : function( dataset ){\n\t //TODO: to underscore template\n\t return $( '')\n\t .attr( 'id', 'dataset-' + dataset.id )\n\t .addClass( 'dataset unpaired' )\n\t .attr( 'draggable', true )\n\t .addClass( dataset.selected? 'selected': '' )\n\t .append( $( '' ).addClass( 'dataset-name' ).text( dataset.name ) )\n\t //??\n\t .data( 'dataset', dataset );\n\t },\n\t /** render the button that may go between unpaired datasets, allowing the user to pair a row */\n\t _renderPairButton : function(){\n\t //TODO: *not* a dataset - don't pretend like it is\n\t return $( '').addClass( 'dataset unpaired' )\n\t .append( $( '' ).addClass( 'dataset-name' ).text( _l( 'Pair these datasets' ) ) );\n\t },\n\t /** a message to display when no unpaired left */\n\t _renderUnpairedEmpty : function(){\n\t //this.debug( '-- renderUnpairedEmpty' );\n\t var $msg = $( '' )\n\t .text( '(' + _l( 'no remaining unpaired datasets' ) + ')' );\n\t this.$( '.unpaired-columns .paired-column .column-datasets' ).empty().prepend( $msg );\n\t return $msg;\n\t },\n\t /** a message to display when no unpaired can be shown with the current filters */\n\t _renderUnpairedNotShown : function(){\n\t //this.debug( '-- renderUnpairedEmpty' );\n\t var $msg = $( '' )\n\t .text( '(' + _l( 'no datasets were found matching the current filters' ) + ')' );\n\t this.$( '.unpaired-columns .paired-column .column-datasets' ).empty().prepend( $msg );\n\t return $msg;\n\t },\n\t /** try to detect if the unpaired section has a scrollbar and adjust left column for better centering of all */\n\t _adjUnpairedOnScrollbar : function(){\n\t var $unpairedColumns = this.$( '.unpaired-columns' ).last(),\n\t $firstDataset = this.$( '.unpaired-columns .reverse-column .dataset' ).first();\n\t if( !$firstDataset.length ){ return; }\n\t var ucRight = $unpairedColumns.offset().left + $unpairedColumns.outerWidth(),\n\t dsRight = $firstDataset.offset().left + $firstDataset.outerWidth(),\n\t rightDiff = Math.floor( ucRight ) - Math.floor( dsRight );\n\t //this.debug( 'rightDiff:', ucRight, '-', dsRight, '=', rightDiff );\n\t this.$( '.unpaired-columns .forward-column' )\n\t .css( 'margin-left', ( rightDiff > 0 )? rightDiff: 0 );\n\t },\n\t\n\t /** render the paired section and update counts of paired datasets */\n\t _renderPaired : function( speed, callback ){\n\t //this.debug( '-- _renderPaired' );\n\t this.$( '.paired-column-title .title' ).text([ this.paired.length, _l( 'paired' ) ].join( ' ' ) );\n\t // show/hide the unpair all link\n\t this.$( '.unpair-all-link' ).toggle( this.paired.length !== 0 );\n\t if( this.paired.length === 0 ){\n\t this._renderPairedEmpty();\n\t return;\n\t //TODO: would be best to return here (the $columns)\n\t } else {\n\t // show/hide 'remove extensions link' when any paired and they seem to have extensions\n\t this.$( '.remove-extensions-link' ).show();\n\t }\n\t\n\t this.$( '.paired-columns .column-datasets' ).empty();\n\t var creator = this;\n\t this.paired.forEach( function( pair, i ){\n\t //TODO: cache these?\n\t var pairView = new PairView({ pair: pair });\n\t creator.$( '.paired-columns .column-datasets' )\n\t .append( pairView.render().$el )\n\t .append([\n\t ''\n\t ].join( '' ));\n\t });\n\t },\n\t /** a message to display when none paired */\n\t _renderPairedEmpty : function(){\n\t var $msg = $( '' )\n\t .text( '(' + _l( 'no paired datasets yet' ) + ')' );\n\t this.$( '.paired-columns .column-datasets' ).empty().prepend( $msg );\n\t return $msg;\n\t },\n\t\n\t /** render the footer, completion controls, and cancel controls */\n\t _renderFooter : function( speed, callback ){\n\t var $footer = this.$( '.footer' ).empty().html( PairedCollectionCreator.templates.footer() );\n\t this.$( '.remove-extensions' ).prop( 'checked', this.removeExtensions );\n\t if( typeof this.oncancel === 'function' ){\n\t this.$( '.cancel-create.btn' ).show();\n\t }\n\t return $footer;\n\t },\n\t\n\t /** add any jQuery/bootstrap/custom plugins to elements rendered */\n\t _addPluginComponents : function(){\n\t this._chooseFiltersPopover( '.choose-filters-link' );\n\t this.$( '.help-content i' ).hoverhighlight( '.collection-creator', 'rgba( 64, 255, 255, 1.0 )' );\n\t },\n\t\n\t /** build a filter selection popover allowing selection of common filter pairs */\n\t _chooseFiltersPopover : function( selector ){\n\t function filterChoice( val1, val2 ){\n\t return [\n\t ''\n\t ].join('');\n\t }\n\t var $popoverContent = $( _.template([\n\t '
', _l([\n\t 'Collections of paired datasets are ordered lists of dataset pairs (often forward and reverse reads). ',\n\t 'These collections can be passed to tools and workflows in order to have analyses done on each member of ',\n\t 'the entire group. This interface allows you to create a collection, choose which datasets are paired, ',\n\t 'and re-order the final collection.'\n\t ].join( '' )), '
',\n\t '', _l([\n\t 'Unpaired datasets are shown in the unpaired section ',\n\t '(hover over the underlined words to highlight below). ',\n\t 'Paired datasets are shown in the paired section.',\n\t '
', _l([\n\t '
', _l([\n\t 'To unpair individual dataset pairs, click the ',\n\t 'unpair buttons ( ). ',\n\t 'Click the \"Unpair all\" link to unpair all pairs.'\n\t ].join( '' )), '
',\n\t '', _l([\n\t 'You can include or remove the file extensions (e.g. \".fastq\") from your pair names by toggling the ',\n\t '\"Remove file extensions from pair names?\" control.'\n\t ].join( '' )), '
',\n\t '', _l([\n\t 'Once your collection is complete, enter a name and ',\n\t 'click \"Create list\". ',\n\t '(Note: you do not have to pair all unpaired datasets to finish.)'\n\t ].join( '' )), '
'\n\t ].join(''))\n\t};\n\t\n\t\n\t//=============================================================================\n\t/** a modal version of the paired collection creator */\n\tvar pairedCollectionCreatorModal = function _pairedCollectionCreatorModal( datasets, options ){\n\t\n\t var deferred = jQuery.Deferred(),\n\t creator;\n\t\n\t options = _.defaults( options || {}, {\n\t datasets : datasets,\n\t oncancel : function(){\n\t Galaxy.modal.hide();\n\t deferred.reject( 'cancelled' );\n\t },\n\t oncreate : function( creator, response ){\n\t Galaxy.modal.hide();\n\t deferred.resolve( response );\n\t }\n\t });\n\t\n\t if( !window.Galaxy || !Galaxy.modal ){\n\t throw new Error( 'Galaxy or Galaxy.modal not found' );\n\t }\n\t\n\t creator = new PairedCollectionCreator( options );\n\t Galaxy.modal.show({\n\t title : 'Create a collection of paired datasets',\n\t body : creator.$el,\n\t width : '80%',\n\t height : '800px',\n\t closing_events: true\n\t });\n\t creator.render();\n\t window.creator = creator;\n\t\n\t //TODO: remove modal header\n\t return deferred;\n\t};\n\t\n\t\n\t//=============================================================================\n\tfunction createListOfPairsCollection( collection ){\n\t var elements = collection.toJSON();\n\t//TODO: validate elements\n\t return pairedCollectionCreatorModal( elements, {\n\t historyId : collection.historyId\n\t });\n\t}\n\t\n\t\n\t//=============================================================================\n\t return {\n\t PairedCollectionCreator : PairedCollectionCreator,\n\t pairedCollectionCreatorModal : pairedCollectionCreatorModal,\n\t createListOfPairsCollection : createListOfPairsCollection\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1), __webpack_require__(1)))\n\n/***/ },\n/* 105 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, jQuery) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(30),\n\t __webpack_require__(38),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( LIST_CREATOR, HDCA, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'collections';\n\t/*==============================================================================\n\tTODO:\n\t the paired creator doesn't really mesh with the list creator as parent\n\t it may be better to make an abstract super class for both\n\t composites may inherit from this (or vis-versa)\n\t PairedDatasetCollectionElementView doesn't make a lot of sense\n\t\n\t==============================================================================*/\n\t/** */\n\tvar PairedDatasetCollectionElementView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n\t _logNamespace : logNamespace,\n\t\n\t//TODO: use proper class (DatasetDCE or NestedDCDCE (or the union of both))\n\t tagName : 'li',\n\t className : 'collection-element',\n\t\n\t initialize : function( attributes ){\n\t this.element = attributes.element || {};\n\t this.identifier = attributes.identifier;\n\t },\n\t\n\t render : function(){\n\t this.$el\n\t .attr( 'data-element-id', this.element.id )\n\t .html( this.template({ identifier: this.identifier, element: this.element }) );\n\t return this;\n\t },\n\t\n\t //TODO: lots of unused space in the element - possibly load details and display them horiz.\n\t template : _.template([\n\t '<%- identifier %>',\n\t '<%- element.name %>',\n\t ].join('')),\n\t\n\t /** remove the DOM and any listeners */\n\t destroy : function(){\n\t this.off();\n\t this.$el.remove();\n\t },\n\t\n\t /** string rep */\n\t toString : function(){\n\t return 'DatasetCollectionElementView()';\n\t }\n\t});\n\t\n\t\n\t// ============================================================================\n\tvar _super = LIST_CREATOR.ListCollectionCreator;\n\t\n\t/** An interface for building collections.\n\t */\n\tvar PairCollectionCreator = _super.extend({\n\t\n\t /** the class used to display individual elements */\n\t elementViewClass : PairedDatasetCollectionElementView,\n\t /** the class this creator will create and save */\n\t collectionClass : HDCA.HistoryPairDatasetCollection,\n\t className : 'pair-collection-creator collection-creator flex-row-container',\n\t\n\t /** override to no-op */\n\t _mangleDuplicateNames : function(){},\n\t\n\t // TODO: this whole pattern sucks. There needs to be two classes of problem area:\n\t // bad inital choices and\n\t // when the user has painted his/her self into a corner during creation/use-of-the-creator\n\t /** render the entire interface */\n\t render : function( speed, callback ){\n\t if( this.workingElements.length === 2 ){\n\t return _super.prototype.render.call( this, speed, callback );\n\t }\n\t return this._renderInvalid( speed, callback );\n\t },\n\t\n\t // ------------------------------------------------------------------------ rendering elements\n\t /** render forward/reverse */\n\t _renderList : function( speed, callback ){\n\t //this.debug( '-- _renderList' );\n\t //precondition: there are two valid elements in workingElements\n\t var creator = this,\n\t $tmp = jQuery( '' ),\n\t $list = creator.$list();\n\t\n\t // lose the original views, create the new, append all at once, then call their renders\n\t _.each( this.elementViews, function( view ){\n\t view.destroy();\n\t creator.removeElementView( view );\n\t });\n\t $tmp.append( creator._createForwardElementView().$el );\n\t $tmp.append( creator._createReverseElementView().$el );\n\t $list.empty().append( $tmp.children() );\n\t _.invoke( creator.elementViews, 'render' );\n\t },\n\t\n\t /** create the forward element view */\n\t _createForwardElementView : function(){\n\t return this._createElementView( this.workingElements[0], { identifier: 'forward' } );\n\t },\n\t\n\t /** create the forward element view */\n\t _createReverseElementView : function(){\n\t return this._createElementView( this.workingElements[1], { identifier: 'reverse' } );\n\t },\n\t\n\t /** create an element view, cache in elementViews, and return */\n\t _createElementView : function( element, options ){\n\t var elementView = new this.elementViewClass( _.extend( options, {\n\t element : element,\n\t }));\n\t this.elementViews.push( elementView );\n\t return elementView;\n\t },\n\t\n\t /** swap the forward, reverse elements and re-render */\n\t swap : function(){\n\t this.workingElements = [\n\t this.workingElements[1],\n\t this.workingElements[0],\n\t ];\n\t this._renderList();\n\t },\n\t\n\t events : _.extend( _.clone( _super.prototype.events ), {\n\t 'click .swap' : 'swap',\n\t }),\n\t\n\t // ------------------------------------------------------------------------ templates\n\t //TODO: move to require text plugin and load these as text\n\t //TODO: underscore currently unnecc. bc no vars are used\n\t //TODO: better way of localizing text-nodes in long strings\n\t /** underscore template fns attached to class */\n\t templates : _.extend( _.clone( _super.prototype.templates ), {\n\t /** the middle: element list */\n\t middle : _.template([\n\t '', _l([\n\t 'Pair collections are permanent collections containing two datasets: one forward and one reverse. ',\n\t 'Often these are forward and reverse reads. The pair collections can be passed to tools and ',\n\t 'workflows in order to have analyses done on both datasets. This interface allows ',\n\t 'you to create a pair, name it, and swap which is forward and which reverse.'\n\t ].join( '' )), '
',\n\t '', _l([\n\t 'Once your collection is complete, enter a name and ',\n\t 'click \"Create list\".'\n\t ].join( '' )), '
'\n\t ].join('')),\n\t\n\t /** a simplified page communicating what went wrong and why the user needs to reselect something else */\n\t invalidInitial : _.template([\n\t '' + this.progressive + '...
';\n\t this.modal.$( '.modal-body' ).empty().append( indicator ).css({ 'margin-top': '8px' });\n\t },\n\t\n\t // (sorta) public interface - display the modal, render the form, and potentially copy the history\n\t // returns a jQuery.Deferred done->history copied, fail->user cancelled\n\t dialog : function _dialog( modal, history, options ){\n\t options = options || {};\n\t\n\t var dialog = this,\n\t deferred = jQuery.Deferred(),\n\t // TODO: getting a little byzantine here\n\t defaultCopyNameFn = options.nameFn || this.defaultName,\n\t defaultCopyName = defaultCopyNameFn({ name: history.get( 'name' ) }),\n\t // TODO: these two might be simpler as one 3 state option (all,active,no-choice)\n\t defaultCopyWhat = options.allDatasets? 'copy-all' : 'copy-non-deleted',\n\t allowAll = !_.isUndefined( options.allowAll )? options.allowAll : true,\n\t autoClose = !_.isUndefined( options.autoClose )? options.autoClose : true;\n\t\n\t this.modal = modal;\n\t\n\t\n\t // validate the name and copy if good\n\t function checkNameAndCopy(){\n\t var name = modal.$( '#copy-modal-title' ).val();\n\t if( !name ){\n\t modal.$( '.invalid-title' ).show();\n\t return;\n\t }\n\t // get further settings, shut down and indicate the ajax call, then hide and resolve/reject\n\t var copyAllDatasets = modal.$( 'input[name=\"copy-what\"]:checked' ).val() === 'copy-all';\n\t modal.$( 'button' ).prop( 'disabled', true );\n\t dialog._showAjaxIndicator();\n\t history.copy( true, name, copyAllDatasets )\n\t .done( function( response ){\n\t deferred.resolve( response );\n\t })\n\t .fail( function( xhr, status, message ){\n\t var options = { name: name, copyAllDatasets: copyAllDatasets };\n\t ERROR_MODAL.ajaxErrorModal( history, xhr, options, dialog.errorMessage );\n\t deferred.rejectWith( deferred, arguments );\n\t })\n\t .done( function(){\n\t if( autoClose ){ modal.hide(); }\n\t });\n\t }\n\t\n\t var originalClosingCallback = options.closing_callback;\n\t modal.show( _.extend( options, {\n\t title : this.title({ name: history.get( 'name' ) }),\n\t body : $( dialog._template({\n\t name : defaultCopyName,\n\t isAnon : Galaxy.user.isAnonymous(),\n\t allowAll : allowAll,\n\t copyWhat : defaultCopyWhat,\n\t activeLabel : this.activeLabel,\n\t allLabel : this.allLabel,\n\t anonWarning : this.anonWarning,\n\t })),\n\t buttons : _.object([\n\t [ _l( 'Cancel' ), function(){ modal.hide(); } ],\n\t [ this.submitLabel, checkNameAndCopy ]\n\t ]),\n\t height : 'auto',\n\t closing_events : true,\n\t closing_callback: function _historyCopyClose( cancelled ){\n\t if( cancelled ){\n\t deferred.reject({ cancelled : true });\n\t }\n\t if( originalClosingCallback ){\n\t originalClosingCallback( cancelled );\n\t }\n\t }\n\t }));\n\t\n\t // set the default dataset copy, autofocus the title, and set up for a simple return\n\t modal.$( '#copy-modal-title' ).focus().select();\n\t modal.$( '#copy-modal-title' ).on( 'keydown', function( ev ){\n\t if( ev.keyCode === 13 ){\n\t ev.preventDefault();\n\t checkNameAndCopy();\n\t }\n\t });\n\t\n\t return deferred;\n\t },\n\t};\n\t\n\t//==============================================================================\n\t// maintain the (slight) distinction between copy and import\n\t/**\n\t * Subclass CopyDialog to use the import language.\n\t */\n\tvar ImportDialog = _.extend( {}, CopyDialog, {\n\t defaultName : _.template( \"imported: <%- name %>\" ),\n\t title : _.template( _l( 'Importing history' ) + ' \"<%- name %>\"' ),\n\t submitLabel : _l( 'Import' ),\n\t errorMessage : _l( 'History could not be imported.' ),\n\t progressive : _l( 'Importing history' ),\n\t activeLabel : _l( 'Import only the active, non-deleted datasets' ),\n\t allLabel : _l( 'Import all datasets including deleted ones' ),\n\t anonWarning : _l( 'As an anonymous user, unless you login or register, you will lose your current history ' ) +\n\t _l( 'after importing this history. ' ),\n\t\n\t});\n\t\n\t//==============================================================================\n\t/**\n\t * Main interface for both history import and history copy dialogs.\n\t * @param {Backbone.Model} history the history to copy\n\t * @param {Object} options a hash\n\t * @return {jQuery.Deferred} promise that fails on close and succeeds on copy\n\t *\n\t * options:\n\t * (this object is also passed to the modal used to display the dialog and accepts modal options)\n\t * {Function} nameFn if defined, use this to build the default name shown to the user\n\t * (the fn is passed: {name:| ' +\n\t ' | ' +\n\t ' | Description | ' +\n\t 'Name | ' +\n\t 'Size | ' +\n\t 'Settings | ' +\n\t 'Status | ' +\n\t '
|---|
| Name | ' +\n\t 'Size | ' +\n\t 'Type | ' +\n\t 'Genome | ' +\n\t 'Settings | ' +\n\t 'Status | ' +\n\t '' +\n\t ' |
|---|
\" + this.formattedReference() + \"
\" );\n return this;\n },\n formattedReference: function() {\n var model = this.model;\n var entryType = model.entryType();\n var fields = model.fields();\n\n var ref = \"\";\n // Code inspired by...\n // https://github.com/vkaravir/bib-publication-list/blob/master/src/bib-publication-list.js\n var authorsAndYear = this._asSentence( (fields.author ? fields.author : \"\") + (fields.year ? (\" (\" + fields.year + \")\") : \"\") ) + \" \";\n var title = fields.title || \"\";\n var pages = fields.pages ? (\"pp. \" + fields.pages) : \"\";\n var address = fields.address;\n if( entryType == \"article\" ) {\n var volume = (fields.volume ? fields.volume : \"\") +\n (fields.number ? ( \" (\" + fields.number + \")\" ) : \"\") +\n (pages ? \", \" + pages : \"\");\n ref = authorsAndYear + this._asSentence(title) +\n (fields.journal ? (\"In \" + fields.journal + \", \") : \"\") +\n this._asSentence(volume) + \n this._asSentence(fields.address) +\n \"<\\/em>\";\n } else if( entryType == \"inproceedings\" || entryType == \"proceedings\" ) {\n ref = authorsAndYear + \n this._asSentence(title) + \n (fields.booktitle ? (\"In \" + fields.booktitle + \", \") : \"\") +\n (pages ? pages : \"\") +\n (address ? \", \" + address : \"\") + \n \".<\\/em>\";\n } else if( entryType == \"mastersthesis\" || entryType == \"phdthesis\" ) {\n ref = authorsAndYear + this._asSentence(title) +\n (fields.howpublished ? fields.howpublished + \". \" : \"\") +\n (fields.note ? fields.note + \".\" : \"\");\n } else if( entryType == \"techreport\" ) {\n ref = authorsAndYear + this._asSentence(title) +\n this._asSentence(fields.institution) +\n this._asSentence(fields.number) +\n this._asSentence(fields.type);\n } else if( entryType == \"book\" || entryType == \"inbook\" || entryType == \"incollection\" ) {\n ref = authorsAndYear + \" \" + this._formatBookInfo(fields);\n } else {\n ref = authorsAndYear + \" \" + this._asSentence(title) +\n this._asSentence(fields.howpublished) +\n this._asSentence(fields.note);\n }\n var doiUrl = \"\";\n if( fields.doi ) {\n doiUrl = 'http://dx.doi.org/' + fields.doi;\n ref += '[doi:' + fields.doi + \"]\";\n }\n var url = fields.url || doiUrl;\n if( url ) {\n ref += '[Link]';\n }\n return ref;\n },\n _formatBookInfo: function(fields) {\n var info = \"\";\n if( fields.chapter ) {\n info += fields.chapter + \" in \";\n }\n if( fields.title ) {\n info += \"\" + fields.title + \"<\\/em>\";\n }\n if( fields.editor ) {\n info += \", Edited by \" + fields.editor + \", \";\n }\n if( fields.publisher) {\n info += \", \" + fields.publisher;\n }\n if( fields.pages ) {\n info += \", pp. \" + fields.pages + \"\";\n }\n if( fields.series ) {\n info += \", \" + fields.series + \"<\\/em>\";\n }\n if( fields.volume ) {\n info += \", Vol.\" + fields.volume;\n }\n if( fields.issn ) {\n info += \", ISBN: \" + fields.issn;\n }\n return info + \".\";\n },\n _asSentence: function(str) {\n return (str && str.trim()) ? str + \". \" : \"\";\n }\n});\n\nvar CitationListView = Backbone.View.extend({\n el: '#citations',\n /**\n * Set up view.\n */\n initialize: function() {\n this.listenTo( this.collection, 'add', this.renderCitation );\n },\n\n events: {\n 'click .citations-to-bibtex': 'showBibtex',\n 'click .citations-to-formatted': 'showFormatted'\n },\n\n renderCitation: function( citation ) {\n var citationView = new CitationView( { model: citation } );\n this.$(\".citations-formatted\").append( citationView.render().el );\n var rawTextarea = this.$(\".citations-bibtex-text\");\n rawTextarea.val( rawTextarea.val() + \"\\n\\r\" + citation.attributes.content );\n },\n\n render: function() {\n this.$el.html(this.citationsElement());\n this.collection.each(function( item ){\n this.renderCitation( item );\n }, this);\n this.showFormatted();\n },\n\n showBibtex: function() {\n this.$(\".citations-to-formatted\").show();\n this.$(\".citations-to-bibtex\").hide();\n this.$(\".citations-bibtex\").show();\n this.$(\".citations-formatted\").hide();\n this.$(\".citations-bibtex-text\").select();\n },\n\n showFormatted: function() {\n this.$(\".citations-to-formatted\").hide();\n this.$(\".citations-to-bibtex\").show();\n this.$(\".citations-bibtex\").hide();\n this.$(\".citations-formatted\").show();\n },\n\n partialWarningElement: function() {\n if( this.collection.partial ) {\n return [\n '' + JSON.stringify( xhr.responseJSON ) + '';\n } else {\n content += ': ' + message;\n }\n }\n creator._showAlert( content, 'alert-danger' );\n },\n\n events : {\n // header\n 'click .more-help' : '_clickMoreHelp',\n 'click .less-help' : '_clickLessHelp',\n 'click .main-help' : '_toggleHelp',\n 'click .header .alert button' : '_hideAlert',\n\n 'click .reset' : 'reset',\n 'click .clear-selected' : 'clearSelectedElements',\n\n // elements - selection\n 'click .collection-elements' : 'clearSelectedElements',\n\n // elements - drop target\n // 'dragenter .collection-elements': '_dragenterElements',\n // 'dragleave .collection-elements': '_dragleaveElements',\n 'dragover .collection-elements' : '_dragoverElements',\n 'drop .collection-elements' : '_dropElements',\n\n // these bubble up from the elements as custom events\n 'collection-element.dragstart .collection-elements' : '_elementDragstart',\n 'collection-element.dragend .collection-elements' : '_elementDragend',\n\n // footer\n 'change .collection-name' : '_changeName',\n 'keydown .collection-name' : '_nameCheckForEnter',\n 'click .cancel-create' : function( ev ){\n if( typeof this.oncancel === 'function' ){\n this.oncancel.call( this );\n }\n },\n 'click .create-collection' : '_clickCreate'//,\n },\n\n // ........................................................................ header\n /** expand help */\n _clickMoreHelp : function( ev ){\n ev.stopPropagation();\n this.$( '.main-help' ).addClass( 'expanded' );\n this.$( '.more-help' ).hide();\n },\n /** collapse help */\n _clickLessHelp : function( ev ){\n ev.stopPropagation();\n this.$( '.main-help' ).removeClass( 'expanded' );\n this.$( '.more-help' ).show();\n },\n /** toggle help */\n _toggleHelp : function( ev ){\n ev.stopPropagation();\n this.$( '.main-help' ).toggleClass( 'expanded' );\n this.$( '.more-help' ).toggle();\n },\n\n /** show an alert on the top of the interface containing message (alertClass is bootstrap's alert-*) */\n _showAlert : function( message, alertClass ){\n alertClass = alertClass || 'alert-danger';\n this.$( '.main-help' ).hide();\n this.$( '.header .alert' )\n .attr( 'class', 'alert alert-dismissable' ).addClass( alertClass ).show()\n .find( '.alert-message' ).html( message );\n },\n /** hide the alerts at the top */\n _hideAlert : function( message ){\n this.$( '.main-help' ).show();\n this.$( '.header .alert' ).hide();\n },\n\n // ........................................................................ elements\n /** reset all data to the initial state */\n reset : function(){\n this._instanceSetUp();\n this._elementsSetUp();\n this.render();\n },\n\n /** deselect all elements */\n clearSelectedElements : function( ev ){\n this.$( '.collection-elements .collection-element' ).removeClass( 'selected' );\n this.$( '.collection-elements-controls > .clear-selected' ).hide();\n },\n\n //_dragenterElements : function( ev ){\n // //this.debug( '_dragenterElements:', ev );\n //},\n//TODO: if selected are dragged out of the list area - remove the placeholder - cuz it won't work anyway\n // _dragleaveElements : function( ev ){\n // //this.debug( '_dragleaveElements:', ev );\n // },\n\n /** track the mouse drag over the list adding a placeholder to show where the drop would occur */\n _dragoverElements : function( ev ){\n //this.debug( '_dragoverElements:', ev );\n ev.preventDefault();\n\n var $list = this.$list();\n this._checkForAutoscroll( $list, ev.originalEvent.clientY );\n var $nearest = this._getNearestElement( ev.originalEvent.clientY );\n\n //TODO: no need to re-create - move instead\n this.$( '.element-drop-placeholder' ).remove();\n var $placeholder = $( '' );\n if( !$nearest.length ){\n $list.append( $placeholder );\n } else {\n $nearest.before( $placeholder );\n }\n },\n\n /** If the mouse is near enough to the list's top or bottom, scroll the list */\n _checkForAutoscroll : function( $element, y ){\n var AUTOSCROLL_SPEED = 2,\n offset = $element.offset(),\n scrollTop = $element.scrollTop(),\n upperDist = y - offset.top,\n lowerDist = ( offset.top + $element.outerHeight() ) - y;\n if( upperDist >= 0 && upperDist < this.autoscrollDist ){\n $element.scrollTop( scrollTop - AUTOSCROLL_SPEED );\n } else if( lowerDist >= 0 && lowerDist < this.autoscrollDist ){\n $element.scrollTop( scrollTop + AUTOSCROLL_SPEED );\n }\n },\n\n /** get the nearest element based on the mouse's Y coordinate.\n * If the y is at the end of the list, return an empty jQuery object.\n */\n _getNearestElement : function( y ){\n var WIGGLE = 4,\n lis = this.$( '.collection-elements li.collection-element' ).toArray();\n for( var i=0; i
', _l([\n 'Collections of datasets are permanent, ordered lists of datasets that can be passed to tools and ',\n 'workflows in order to have analyses done on each member of the entire group. This interface allows ',\n 'you to create a collection and re-order the final collection.'\n ].join( '' )), '
',\n '', _l([\n 'Once your collection is complete, enter a name and ',\n 'click \"Create list\".'\n ].join( '' )), '
'\n ].join('')),\n\n /** shown in list when all elements are discarded */\n invalidElements : _.template([\n _l( 'The following selections could not be included due to problems:' ),\n '<%= dataset.peek %>',\n '<% } %>',\n '<% } %>',\n '
| ' +\n ' | Name | ' +\n 'Size | ' +\n 'Created | ' +\n '
|---|
' + JSON.stringify( xhr.responseJSON ) + '';\n } else {\n content += ': ' + message;\n }\n }\n creator._showAlert( content, 'alert-danger' );\n },\n\n // ------------------------------------------------------------------------ rendering\n /** render the entire interface */\n render : function( speed, callback ){\n //this.debug( '-- _render' );\n //this.$el.empty().html( PairedCollectionCreator.templates.main() );\n this.$el.empty().html( PairedCollectionCreator.templates.main() );\n this._renderHeader( speed );\n this._renderMiddle( speed );\n this._renderFooter( speed );\n this._addPluginComponents();\n this.trigger( 'rendered', this );\n return this;\n },\n\n /** render the header section */\n _renderHeader : function( speed, callback ){\n //this.debug( '-- _renderHeader' );\n var $header = this.$( '.header' ).empty().html( PairedCollectionCreator.templates.header() )\n .find( '.help-content' ).prepend( $( PairedCollectionCreator.templates.helpContent() ) );\n\n this._renderFilters();\n return $header;\n },\n /** fill the filter inputs with the filter values */\n _renderFilters : function(){\n return this.$( '.forward-column .column-header input' ).val( this.filters[0] )\n .add( this.$( '.reverse-column .column-header input' ).val( this.filters[1] ) );\n },\n\n /** render the middle including unpaired and paired sections (which may be hidden) */\n _renderMiddle : function( speed, callback ){\n var $middle = this.$( '.middle' ).empty().html( PairedCollectionCreator.templates.middle() );\n\n // (re-) hide the un/paired panels based on instance vars\n if( this.unpairedPanelHidden ){\n this.$( '.unpaired-columns' ).hide();\n } else if( this.pairedPanelHidden ){\n this.$( '.paired-columns' ).hide();\n }\n\n this._renderUnpaired();\n this._renderPaired();\n return $middle;\n },\n /** render the unpaired section, showing datasets accrd. to filters, update the unpaired counts */\n _renderUnpaired : function( speed, callback ){\n //this.debug( '-- _renderUnpaired' );\n var creator = this,\n $fwd, $rev, $prd = [],\n split = this._splitByFilters();\n // update unpaired counts\n this.$( '.forward-column .title' )\n .text([ split[0].length, _l( 'unpaired forward' ) ].join( ' ' ));\n this.$( '.forward-column .unpaired-info' )\n .text( this._renderUnpairedDisplayStr( this.unpaired.length - split[0].length ) );\n this.$( '.reverse-column .title' )\n .text([ split[1].length, _l( 'unpaired reverse' ) ].join( ' ' ));\n this.$( '.reverse-column .unpaired-info' )\n .text( this._renderUnpairedDisplayStr( this.unpaired.length - split[1].length ) );\n\n this.$( '.unpaired-columns .column-datasets' ).empty();\n\n // show/hide the auto pair button if any unpaired are left\n this.$( '.autopair-link' ).toggle( this.unpaired.length !== 0 );\n if( this.unpaired.length === 0 ){\n this._renderUnpairedEmpty();\n return;\n }\n\n // create the dataset dom arrays\n $rev = split[1].map( function( dataset, i ){\n // if there'll be a fwd dataset across the way, add a button to pair the row\n if( ( split[0][ i ] !== undefined )\n && ( split[0][ i ] !== dataset ) ){\n $prd.push( creator._renderPairButton() );\n }\n return creator._renderUnpairedDataset( dataset );\n });\n $fwd = split[0].map( function( dataset ){\n return creator._renderUnpairedDataset( dataset );\n });\n\n if( !$fwd.length && !$rev.length ){\n this._renderUnpairedNotShown();\n return;\n }\n // add to appropo cols\n //TODO: not the best way to render - consider rendering the entire unpaired-columns section in a fragment\n // and swapping out that\n this.$( '.unpaired-columns .forward-column .column-datasets' ).append( $fwd )\n .add( this.$( '.unpaired-columns .paired-column .column-datasets' ).append( $prd ) )\n .add( this.$( '.unpaired-columns .reverse-column .column-datasets' ).append( $rev ) );\n this._adjUnpairedOnScrollbar();\n },\n /** return a string to display the count of filtered out datasets */\n _renderUnpairedDisplayStr : function( numFiltered ){\n return [ '(', numFiltered, ' ', _l( 'filtered out' ), ')' ].join('');\n },\n /** return an unattached jQuery DOM element to represent an unpaired dataset */\n _renderUnpairedDataset : function( dataset ){\n //TODO: to underscore template\n return $( '')\n .attr( 'id', 'dataset-' + dataset.id )\n .addClass( 'dataset unpaired' )\n .attr( 'draggable', true )\n .addClass( dataset.selected? 'selected': '' )\n .append( $( '' ).addClass( 'dataset-name' ).text( dataset.name ) )\n //??\n .data( 'dataset', dataset );\n },\n /** render the button that may go between unpaired datasets, allowing the user to pair a row */\n _renderPairButton : function(){\n //TODO: *not* a dataset - don't pretend like it is\n return $( '').addClass( 'dataset unpaired' )\n .append( $( '' ).addClass( 'dataset-name' ).text( _l( 'Pair these datasets' ) ) );\n },\n /** a message to display when no unpaired left */\n _renderUnpairedEmpty : function(){\n //this.debug( '-- renderUnpairedEmpty' );\n var $msg = $( '' )\n .text( '(' + _l( 'no remaining unpaired datasets' ) + ')' );\n this.$( '.unpaired-columns .paired-column .column-datasets' ).empty().prepend( $msg );\n return $msg;\n },\n /** a message to display when no unpaired can be shown with the current filters */\n _renderUnpairedNotShown : function(){\n //this.debug( '-- renderUnpairedEmpty' );\n var $msg = $( '' )\n .text( '(' + _l( 'no datasets were found matching the current filters' ) + ')' );\n this.$( '.unpaired-columns .paired-column .column-datasets' ).empty().prepend( $msg );\n return $msg;\n },\n /** try to detect if the unpaired section has a scrollbar and adjust left column for better centering of all */\n _adjUnpairedOnScrollbar : function(){\n var $unpairedColumns = this.$( '.unpaired-columns' ).last(),\n $firstDataset = this.$( '.unpaired-columns .reverse-column .dataset' ).first();\n if( !$firstDataset.length ){ return; }\n var ucRight = $unpairedColumns.offset().left + $unpairedColumns.outerWidth(),\n dsRight = $firstDataset.offset().left + $firstDataset.outerWidth(),\n rightDiff = Math.floor( ucRight ) - Math.floor( dsRight );\n //this.debug( 'rightDiff:', ucRight, '-', dsRight, '=', rightDiff );\n this.$( '.unpaired-columns .forward-column' )\n .css( 'margin-left', ( rightDiff > 0 )? rightDiff: 0 );\n },\n\n /** render the paired section and update counts of paired datasets */\n _renderPaired : function( speed, callback ){\n //this.debug( '-- _renderPaired' );\n this.$( '.paired-column-title .title' ).text([ this.paired.length, _l( 'paired' ) ].join( ' ' ) );\n // show/hide the unpair all link\n this.$( '.unpair-all-link' ).toggle( this.paired.length !== 0 );\n if( this.paired.length === 0 ){\n this._renderPairedEmpty();\n return;\n //TODO: would be best to return here (the $columns)\n } else {\n // show/hide 'remove extensions link' when any paired and they seem to have extensions\n this.$( '.remove-extensions-link' ).show();\n }\n\n this.$( '.paired-columns .column-datasets' ).empty();\n var creator = this;\n this.paired.forEach( function( pair, i ){\n //TODO: cache these?\n var pairView = new PairView({ pair: pair });\n creator.$( '.paired-columns .column-datasets' )\n .append( pairView.render().$el )\n .append([\n ''\n ].join( '' ));\n });\n },\n /** a message to display when none paired */\n _renderPairedEmpty : function(){\n var $msg = $( '' )\n .text( '(' + _l( 'no paired datasets yet' ) + ')' );\n this.$( '.paired-columns .column-datasets' ).empty().prepend( $msg );\n return $msg;\n },\n\n /** render the footer, completion controls, and cancel controls */\n _renderFooter : function( speed, callback ){\n var $footer = this.$( '.footer' ).empty().html( PairedCollectionCreator.templates.footer() );\n this.$( '.remove-extensions' ).prop( 'checked', this.removeExtensions );\n if( typeof this.oncancel === 'function' ){\n this.$( '.cancel-create.btn' ).show();\n }\n return $footer;\n },\n\n /** add any jQuery/bootstrap/custom plugins to elements rendered */\n _addPluginComponents : function(){\n this._chooseFiltersPopover( '.choose-filters-link' );\n this.$( '.help-content i' ).hoverhighlight( '.collection-creator', 'rgba( 64, 255, 255, 1.0 )' );\n },\n\n /** build a filter selection popover allowing selection of common filter pairs */\n _chooseFiltersPopover : function( selector ){\n function filterChoice( val1, val2 ){\n return [\n ''\n ].join('');\n }\n var $popoverContent = $( _.template([\n '
', _l([\n 'Collections of paired datasets are ordered lists of dataset pairs (often forward and reverse reads). ',\n 'These collections can be passed to tools and workflows in order to have analyses done on each member of ',\n 'the entire group. This interface allows you to create a collection, choose which datasets are paired, ',\n 'and re-order the final collection.'\n ].join( '' )), '
',\n '', _l([\n 'Unpaired datasets are shown in the unpaired section ',\n '(hover over the underlined words to highlight below). ',\n 'Paired datasets are shown in the paired section.',\n '
', _l([\n '
', _l([\n 'To unpair individual dataset pairs, click the ',\n 'unpair buttons ( ). ',\n 'Click the \"Unpair all\" link to unpair all pairs.'\n ].join( '' )), '
',\n '', _l([\n 'You can include or remove the file extensions (e.g. \".fastq\") from your pair names by toggling the ',\n '\"Remove file extensions from pair names?\" control.'\n ].join( '' )), '
',\n '', _l([\n 'Once your collection is complete, enter a name and ',\n 'click \"Create list\". ',\n '(Note: you do not have to pair all unpaired datasets to finish.)'\n ].join( '' )), '
'\n ].join(''))\n};\n\n\n//=============================================================================\n/** a modal version of the paired collection creator */\nvar pairedCollectionCreatorModal = function _pairedCollectionCreatorModal( datasets, options ){\n\n var deferred = jQuery.Deferred(),\n creator;\n\n options = _.defaults( options || {}, {\n datasets : datasets,\n oncancel : function(){\n Galaxy.modal.hide();\n deferred.reject( 'cancelled' );\n },\n oncreate : function( creator, response ){\n Galaxy.modal.hide();\n deferred.resolve( response );\n }\n });\n\n if( !window.Galaxy || !Galaxy.modal ){\n throw new Error( 'Galaxy or Galaxy.modal not found' );\n }\n\n creator = new PairedCollectionCreator( options );\n Galaxy.modal.show({\n title : 'Create a collection of paired datasets',\n body : creator.$el,\n width : '80%',\n height : '800px',\n closing_events: true\n });\n creator.render();\n window.creator = creator;\n\n //TODO: remove modal header\n return deferred;\n};\n\n\n//=============================================================================\nfunction createListOfPairsCollection( collection ){\n var elements = collection.toJSON();\n//TODO: validate elements\n return pairedCollectionCreatorModal( elements, {\n historyId : collection.historyId\n });\n}\n\n\n//=============================================================================\n return {\n PairedCollectionCreator : PairedCollectionCreator,\n pairedCollectionCreatorModal : pairedCollectionCreatorModal,\n createListOfPairsCollection : createListOfPairsCollection\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/collection/list-of-pairs-collection-creator.js\n ** module id = 104\n ** module chunks = 3\n **/","define([\n \"mvc/collection/list-collection-creator\",\n \"mvc/history/hdca-model\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( LIST_CREATOR, HDCA, BASE_MVC, _l ){\n\n'use strict';\n\nvar logNamespace = 'collections';\n/*==============================================================================\nTODO:\n the paired creator doesn't really mesh with the list creator as parent\n it may be better to make an abstract super class for both\n composites may inherit from this (or vis-versa)\n PairedDatasetCollectionElementView doesn't make a lot of sense\n\n==============================================================================*/\n/** */\nvar PairedDatasetCollectionElementView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n _logNamespace : logNamespace,\n\n//TODO: use proper class (DatasetDCE or NestedDCDCE (or the union of both))\n tagName : 'li',\n className : 'collection-element',\n\n initialize : function( attributes ){\n this.element = attributes.element || {};\n this.identifier = attributes.identifier;\n },\n\n render : function(){\n this.$el\n .attr( 'data-element-id', this.element.id )\n .html( this.template({ identifier: this.identifier, element: this.element }) );\n return this;\n },\n\n //TODO: lots of unused space in the element - possibly load details and display them horiz.\n template : _.template([\n '<%- identifier %>',\n '<%- element.name %>',\n ].join('')),\n\n /** remove the DOM and any listeners */\n destroy : function(){\n this.off();\n this.$el.remove();\n },\n\n /** string rep */\n toString : function(){\n return 'DatasetCollectionElementView()';\n }\n});\n\n\n// ============================================================================\nvar _super = LIST_CREATOR.ListCollectionCreator;\n\n/** An interface for building collections.\n */\nvar PairCollectionCreator = _super.extend({\n\n /** the class used to display individual elements */\n elementViewClass : PairedDatasetCollectionElementView,\n /** the class this creator will create and save */\n collectionClass : HDCA.HistoryPairDatasetCollection,\n className : 'pair-collection-creator collection-creator flex-row-container',\n\n /** override to no-op */\n _mangleDuplicateNames : function(){},\n\n // TODO: this whole pattern sucks. There needs to be two classes of problem area:\n // bad inital choices and\n // when the user has painted his/her self into a corner during creation/use-of-the-creator\n /** render the entire interface */\n render : function( speed, callback ){\n if( this.workingElements.length === 2 ){\n return _super.prototype.render.call( this, speed, callback );\n }\n return this._renderInvalid( speed, callback );\n },\n\n // ------------------------------------------------------------------------ rendering elements\n /** render forward/reverse */\n _renderList : function( speed, callback ){\n //this.debug( '-- _renderList' );\n //precondition: there are two valid elements in workingElements\n var creator = this,\n $tmp = jQuery( '' ),\n $list = creator.$list();\n\n // lose the original views, create the new, append all at once, then call their renders\n _.each( this.elementViews, function( view ){\n view.destroy();\n creator.removeElementView( view );\n });\n $tmp.append( creator._createForwardElementView().$el );\n $tmp.append( creator._createReverseElementView().$el );\n $list.empty().append( $tmp.children() );\n _.invoke( creator.elementViews, 'render' );\n },\n\n /** create the forward element view */\n _createForwardElementView : function(){\n return this._createElementView( this.workingElements[0], { identifier: 'forward' } );\n },\n\n /** create the forward element view */\n _createReverseElementView : function(){\n return this._createElementView( this.workingElements[1], { identifier: 'reverse' } );\n },\n\n /** create an element view, cache in elementViews, and return */\n _createElementView : function( element, options ){\n var elementView = new this.elementViewClass( _.extend( options, {\n element : element,\n }));\n this.elementViews.push( elementView );\n return elementView;\n },\n\n /** swap the forward, reverse elements and re-render */\n swap : function(){\n this.workingElements = [\n this.workingElements[1],\n this.workingElements[0],\n ];\n this._renderList();\n },\n\n events : _.extend( _.clone( _super.prototype.events ), {\n 'click .swap' : 'swap',\n }),\n\n // ------------------------------------------------------------------------ templates\n //TODO: move to require text plugin and load these as text\n //TODO: underscore currently unnecc. bc no vars are used\n //TODO: better way of localizing text-nodes in long strings\n /** underscore template fns attached to class */\n templates : _.extend( _.clone( _super.prototype.templates ), {\n /** the middle: element list */\n middle : _.template([\n '', _l([\n 'Pair collections are permanent collections containing two datasets: one forward and one reverse. ',\n 'Often these are forward and reverse reads. The pair collections can be passed to tools and ',\n 'workflows in order to have analyses done on both datasets. This interface allows ',\n 'you to create a pair, name it, and swap which is forward and which reverse.'\n ].join( '' )), '
',\n '', _l([\n 'Once your collection is complete, enter a name and ',\n 'click \"Create list\".'\n ].join( '' )), '
'\n ].join('')),\n\n /** a simplified page communicating what went wrong and why the user needs to reselect something else */\n invalidInitial : _.template([\n '' + this.progressive + '...
';\n this.modal.$( '.modal-body' ).empty().append( indicator ).css({ 'margin-top': '8px' });\n },\n\n // (sorta) public interface - display the modal, render the form, and potentially copy the history\n // returns a jQuery.Deferred done->history copied, fail->user cancelled\n dialog : function _dialog( modal, history, options ){\n options = options || {};\n\n var dialog = this,\n deferred = jQuery.Deferred(),\n // TODO: getting a little byzantine here\n defaultCopyNameFn = options.nameFn || this.defaultName,\n defaultCopyName = defaultCopyNameFn({ name: history.get( 'name' ) }),\n // TODO: these two might be simpler as one 3 state option (all,active,no-choice)\n defaultCopyWhat = options.allDatasets? 'copy-all' : 'copy-non-deleted',\n allowAll = !_.isUndefined( options.allowAll )? options.allowAll : true,\n autoClose = !_.isUndefined( options.autoClose )? options.autoClose : true;\n\n this.modal = modal;\n\n\n // validate the name and copy if good\n function checkNameAndCopy(){\n var name = modal.$( '#copy-modal-title' ).val();\n if( !name ){\n modal.$( '.invalid-title' ).show();\n return;\n }\n // get further settings, shut down and indicate the ajax call, then hide and resolve/reject\n var copyAllDatasets = modal.$( 'input[name=\"copy-what\"]:checked' ).val() === 'copy-all';\n modal.$( 'button' ).prop( 'disabled', true );\n dialog._showAjaxIndicator();\n history.copy( true, name, copyAllDatasets )\n .done( function( response ){\n deferred.resolve( response );\n })\n .fail( function( xhr, status, message ){\n var options = { name: name, copyAllDatasets: copyAllDatasets };\n ERROR_MODAL.ajaxErrorModal( history, xhr, options, dialog.errorMessage );\n deferred.rejectWith( deferred, arguments );\n })\n .done( function(){\n if( autoClose ){ modal.hide(); }\n });\n }\n\n var originalClosingCallback = options.closing_callback;\n modal.show( _.extend( options, {\n title : this.title({ name: history.get( 'name' ) }),\n body : $( dialog._template({\n name : defaultCopyName,\n isAnon : Galaxy.user.isAnonymous(),\n allowAll : allowAll,\n copyWhat : defaultCopyWhat,\n activeLabel : this.activeLabel,\n allLabel : this.allLabel,\n anonWarning : this.anonWarning,\n })),\n buttons : _.object([\n [ _l( 'Cancel' ), function(){ modal.hide(); } ],\n [ this.submitLabel, checkNameAndCopy ]\n ]),\n height : 'auto',\n closing_events : true,\n closing_callback: function _historyCopyClose( cancelled ){\n if( cancelled ){\n deferred.reject({ cancelled : true });\n }\n if( originalClosingCallback ){\n originalClosingCallback( cancelled );\n }\n }\n }));\n\n // set the default dataset copy, autofocus the title, and set up for a simple return\n modal.$( '#copy-modal-title' ).focus().select();\n modal.$( '#copy-modal-title' ).on( 'keydown', function( ev ){\n if( ev.keyCode === 13 ){\n ev.preventDefault();\n checkNameAndCopy();\n }\n });\n\n return deferred;\n },\n};\n\n//==============================================================================\n// maintain the (slight) distinction between copy and import\n/**\n * Subclass CopyDialog to use the import language.\n */\nvar ImportDialog = _.extend( {}, CopyDialog, {\n defaultName : _.template( \"imported: <%- name %>\" ),\n title : _.template( _l( 'Importing history' ) + ' \"<%- name %>\"' ),\n submitLabel : _l( 'Import' ),\n errorMessage : _l( 'History could not be imported.' ),\n progressive : _l( 'Importing history' ),\n activeLabel : _l( 'Import only the active, non-deleted datasets' ),\n allLabel : _l( 'Import all datasets including deleted ones' ),\n anonWarning : _l( 'As an anonymous user, unless you login or register, you will lose your current history ' ) +\n _l( 'after importing this history. ' ),\n\n});\n\n//==============================================================================\n/**\n * Main interface for both history import and history copy dialogs.\n * @param {Backbone.Model} history the history to copy\n * @param {Object} options a hash\n * @return {jQuery.Deferred} promise that fails on close and succeeds on copy\n *\n * options:\n * (this object is also passed to the modal used to display the dialog and accepts modal options)\n * {Function} nameFn if defined, use this to build the default name shown to the user\n * (the fn is passed: {name:| ' +\n ' | ' +\n ' | Description | ' +\n 'Name | ' +\n 'Size | ' +\n 'Settings | ' +\n 'Status | ' +\n '
|---|
| Name | ' +\n 'Size | ' +\n 'Type | ' +\n 'Genome | ' +\n 'Settings | ' +\n 'Status | ' +\n '' +\n ' |
|---|
\" + this.formattedReference() + \"
\" );\n\t return this;\n\t },\n\t formattedReference: function() {\n\t var model = this.model;\n\t var entryType = model.entryType();\n\t var fields = model.fields();\n\t\n\t var ref = \"\";\n\t // Code inspired by...\n\t // https://github.com/vkaravir/bib-publication-list/blob/master/src/bib-publication-list.js\n\t var authorsAndYear = this._asSentence( (fields.author ? fields.author : \"\") + (fields.year ? (\" (\" + fields.year + \")\") : \"\") ) + \" \";\n\t var title = fields.title || \"\";\n\t var pages = fields.pages ? (\"pp. \" + fields.pages) : \"\";\n\t var address = fields.address;\n\t if( entryType == \"article\" ) {\n\t var volume = (fields.volume ? fields.volume : \"\") +\n\t (fields.number ? ( \" (\" + fields.number + \")\" ) : \"\") +\n\t (pages ? \", \" + pages : \"\");\n\t ref = authorsAndYear + this._asSentence(title) +\n\t (fields.journal ? (\"In \" + fields.journal + \", \") : \"\") +\n\t this._asSentence(volume) + \n\t this._asSentence(fields.address) +\n\t \"<\\/em>\";\n\t } else if( entryType == \"inproceedings\" || entryType == \"proceedings\" ) {\n\t ref = authorsAndYear + \n\t this._asSentence(title) + \n\t (fields.booktitle ? (\"In \" + fields.booktitle + \", \") : \"\") +\n\t (pages ? pages : \"\") +\n\t (address ? \", \" + address : \"\") + \n\t \".<\\/em>\";\n\t } else if( entryType == \"mastersthesis\" || entryType == \"phdthesis\" ) {\n\t ref = authorsAndYear + this._asSentence(title) +\n\t (fields.howpublished ? fields.howpublished + \". \" : \"\") +\n\t (fields.note ? fields.note + \".\" : \"\");\n\t } else if( entryType == \"techreport\" ) {\n\t ref = authorsAndYear + this._asSentence(title) +\n\t this._asSentence(fields.institution) +\n\t this._asSentence(fields.number) +\n\t this._asSentence(fields.type);\n\t } else if( entryType == \"book\" || entryType == \"inbook\" || entryType == \"incollection\" ) {\n\t ref = authorsAndYear + \" \" + this._formatBookInfo(fields);\n\t } else {\n\t ref = authorsAndYear + \" \" + this._asSentence(title) +\n\t this._asSentence(fields.howpublished) +\n\t this._asSentence(fields.note);\n\t }\n\t var doiUrl = \"\";\n\t if( fields.doi ) {\n\t doiUrl = 'http://dx.doi.org/' + fields.doi;\n\t ref += '[doi:' + fields.doi + \"]\";\n\t }\n\t var url = fields.url || doiUrl;\n\t if( url ) {\n\t ref += '[Link]';\n\t }\n\t return ref;\n\t },\n\t _formatBookInfo: function(fields) {\n\t var info = \"\";\n\t if( fields.chapter ) {\n\t info += fields.chapter + \" in \";\n\t }\n\t if( fields.title ) {\n\t info += \"\" + fields.title + \"<\\/em>\";\n\t }\n\t if( fields.editor ) {\n\t info += \", Edited by \" + fields.editor + \", \";\n\t }\n\t if( fields.publisher) {\n\t info += \", \" + fields.publisher;\n\t }\n\t if( fields.pages ) {\n\t info += \", pp. \" + fields.pages + \"\";\n\t }\n\t if( fields.series ) {\n\t info += \", \" + fields.series + \"<\\/em>\";\n\t }\n\t if( fields.volume ) {\n\t info += \", Vol.\" + fields.volume;\n\t }\n\t if( fields.issn ) {\n\t info += \", ISBN: \" + fields.issn;\n\t }\n\t return info + \".\";\n\t },\n\t _asSentence: function(str) {\n\t return (str && str.trim()) ? str + \". \" : \"\";\n\t }\n\t});\n\t\n\tvar CitationListView = Backbone.View.extend({\n\t el: '#citations',\n\t /**\n\t * Set up view.\n\t */\n\t initialize: function() {\n\t this.listenTo( this.collection, 'add', this.renderCitation );\n\t },\n\t\n\t events: {\n\t 'click .citations-to-bibtex': 'showBibtex',\n\t 'click .citations-to-formatted': 'showFormatted'\n\t },\n\t\n\t renderCitation: function( citation ) {\n\t var citationView = new CitationView( { model: citation } );\n\t this.$(\".citations-formatted\").append( citationView.render().el );\n\t var rawTextarea = this.$(\".citations-bibtex-text\");\n\t rawTextarea.val( rawTextarea.val() + \"\\n\\r\" + citation.attributes.content );\n\t },\n\t\n\t render: function() {\n\t this.$el.html(this.citationsElement());\n\t this.collection.each(function( item ){\n\t this.renderCitation( item );\n\t }, this);\n\t this.showFormatted();\n\t },\n\t\n\t showBibtex: function() {\n\t this.$(\".citations-to-formatted\").show();\n\t this.$(\".citations-to-bibtex\").hide();\n\t this.$(\".citations-bibtex\").show();\n\t this.$(\".citations-formatted\").hide();\n\t this.$(\".citations-bibtex-text\").select();\n\t },\n\t\n\t showFormatted: function() {\n\t this.$(\".citations-to-formatted\").hide();\n\t this.$(\".citations-to-bibtex\").show();\n\t this.$(\".citations-bibtex\").hide();\n\t this.$(\".citations-formatted\").show();\n\t },\n\t\n\t partialWarningElement: function() {\n\t if( this.collection.partial ) {\n\t return [\n\t '' + JSON.stringify( xhr.responseJSON ) + '';\n\t } else {\n\t content += ': ' + message;\n\t }\n\t }\n\t creator._showAlert( content, 'alert-danger' );\n\t },\n\t\n\t events : {\n\t // header\n\t 'click .more-help' : '_clickMoreHelp',\n\t 'click .less-help' : '_clickLessHelp',\n\t 'click .main-help' : '_toggleHelp',\n\t 'click .header .alert button' : '_hideAlert',\n\t\n\t 'click .reset' : 'reset',\n\t 'click .clear-selected' : 'clearSelectedElements',\n\t\n\t // elements - selection\n\t 'click .collection-elements' : 'clearSelectedElements',\n\t\n\t // elements - drop target\n\t // 'dragenter .collection-elements': '_dragenterElements',\n\t // 'dragleave .collection-elements': '_dragleaveElements',\n\t 'dragover .collection-elements' : '_dragoverElements',\n\t 'drop .collection-elements' : '_dropElements',\n\t\n\t // these bubble up from the elements as custom events\n\t 'collection-element.dragstart .collection-elements' : '_elementDragstart',\n\t 'collection-element.dragend .collection-elements' : '_elementDragend',\n\t\n\t // footer\n\t 'change .collection-name' : '_changeName',\n\t 'keydown .collection-name' : '_nameCheckForEnter',\n\t 'click .cancel-create' : function( ev ){\n\t if( typeof this.oncancel === 'function' ){\n\t this.oncancel.call( this );\n\t }\n\t },\n\t 'click .create-collection' : '_clickCreate'//,\n\t },\n\t\n\t // ........................................................................ header\n\t /** expand help */\n\t _clickMoreHelp : function( ev ){\n\t ev.stopPropagation();\n\t this.$( '.main-help' ).addClass( 'expanded' );\n\t this.$( '.more-help' ).hide();\n\t },\n\t /** collapse help */\n\t _clickLessHelp : function( ev ){\n\t ev.stopPropagation();\n\t this.$( '.main-help' ).removeClass( 'expanded' );\n\t this.$( '.more-help' ).show();\n\t },\n\t /** toggle help */\n\t _toggleHelp : function( ev ){\n\t ev.stopPropagation();\n\t this.$( '.main-help' ).toggleClass( 'expanded' );\n\t this.$( '.more-help' ).toggle();\n\t },\n\t\n\t /** show an alert on the top of the interface containing message (alertClass is bootstrap's alert-*) */\n\t _showAlert : function( message, alertClass ){\n\t alertClass = alertClass || 'alert-danger';\n\t this.$( '.main-help' ).hide();\n\t this.$( '.header .alert' )\n\t .attr( 'class', 'alert alert-dismissable' ).addClass( alertClass ).show()\n\t .find( '.alert-message' ).html( message );\n\t },\n\t /** hide the alerts at the top */\n\t _hideAlert : function( message ){\n\t this.$( '.main-help' ).show();\n\t this.$( '.header .alert' ).hide();\n\t },\n\t\n\t // ........................................................................ elements\n\t /** reset all data to the initial state */\n\t reset : function(){\n\t this._instanceSetUp();\n\t this._elementsSetUp();\n\t this.render();\n\t },\n\t\n\t /** deselect all elements */\n\t clearSelectedElements : function( ev ){\n\t this.$( '.collection-elements .collection-element' ).removeClass( 'selected' );\n\t this.$( '.collection-elements-controls > .clear-selected' ).hide();\n\t },\n\t\n\t //_dragenterElements : function( ev ){\n\t // //this.debug( '_dragenterElements:', ev );\n\t //},\n\t//TODO: if selected are dragged out of the list area - remove the placeholder - cuz it won't work anyway\n\t // _dragleaveElements : function( ev ){\n\t // //this.debug( '_dragleaveElements:', ev );\n\t // },\n\t\n\t /** track the mouse drag over the list adding a placeholder to show where the drop would occur */\n\t _dragoverElements : function( ev ){\n\t //this.debug( '_dragoverElements:', ev );\n\t ev.preventDefault();\n\t\n\t var $list = this.$list();\n\t this._checkForAutoscroll( $list, ev.originalEvent.clientY );\n\t var $nearest = this._getNearestElement( ev.originalEvent.clientY );\n\t\n\t //TODO: no need to re-create - move instead\n\t this.$( '.element-drop-placeholder' ).remove();\n\t var $placeholder = $( '' );\n\t if( !$nearest.length ){\n\t $list.append( $placeholder );\n\t } else {\n\t $nearest.before( $placeholder );\n\t }\n\t },\n\t\n\t /** If the mouse is near enough to the list's top or bottom, scroll the list */\n\t _checkForAutoscroll : function( $element, y ){\n\t var AUTOSCROLL_SPEED = 2,\n\t offset = $element.offset(),\n\t scrollTop = $element.scrollTop(),\n\t upperDist = y - offset.top,\n\t lowerDist = ( offset.top + $element.outerHeight() ) - y;\n\t if( upperDist >= 0 && upperDist < this.autoscrollDist ){\n\t $element.scrollTop( scrollTop - AUTOSCROLL_SPEED );\n\t } else if( lowerDist >= 0 && lowerDist < this.autoscrollDist ){\n\t $element.scrollTop( scrollTop + AUTOSCROLL_SPEED );\n\t }\n\t },\n\t\n\t /** get the nearest element based on the mouse's Y coordinate.\n\t * If the y is at the end of the list, return an empty jQuery object.\n\t */\n\t _getNearestElement : function( y ){\n\t var WIGGLE = 4,\n\t lis = this.$( '.collection-elements li.collection-element' ).toArray();\n\t for( var i=0; i
', _l([\n\t 'Collections of datasets are permanent, ordered lists of datasets that can be passed to tools and ',\n\t 'workflows in order to have analyses done on each member of the entire group. This interface allows ',\n\t 'you to create a collection and re-order the final collection.'\n\t ].join( '' )), '
',\n\t '', _l([\n\t 'Once your collection is complete, enter a name and ',\n\t 'click \"Create list\".'\n\t ].join( '' )), '
'\n\t ].join('')),\n\t\n\t /** shown in list when all elements are discarded */\n\t invalidElements : _.template([\n\t _l( 'The following selections could not be included due to problems:' ),\n\t '<%= dataset.peek %>',\n\t '<% } %>',\n\t '<% } %>',\n\t '
| ' +\n\t ' | Name | ' +\n\t 'Size | ' +\n\t 'Created | ' +\n\t '
|---|
' + JSON.stringify( xhr.responseJSON ) + '';\n\t } else {\n\t content += ': ' + message;\n\t }\n\t }\n\t creator._showAlert( content, 'alert-danger' );\n\t },\n\t\n\t // ------------------------------------------------------------------------ rendering\n\t /** render the entire interface */\n\t render : function( speed, callback ){\n\t //this.debug( '-- _render' );\n\t //this.$el.empty().html( PairedCollectionCreator.templates.main() );\n\t this.$el.empty().html( PairedCollectionCreator.templates.main() );\n\t this._renderHeader( speed );\n\t this._renderMiddle( speed );\n\t this._renderFooter( speed );\n\t this._addPluginComponents();\n\t this.trigger( 'rendered', this );\n\t return this;\n\t },\n\t\n\t /** render the header section */\n\t _renderHeader : function( speed, callback ){\n\t //this.debug( '-- _renderHeader' );\n\t var $header = this.$( '.header' ).empty().html( PairedCollectionCreator.templates.header() )\n\t .find( '.help-content' ).prepend( $( PairedCollectionCreator.templates.helpContent() ) );\n\t\n\t this._renderFilters();\n\t return $header;\n\t },\n\t /** fill the filter inputs with the filter values */\n\t _renderFilters : function(){\n\t return this.$( '.forward-column .column-header input' ).val( this.filters[0] )\n\t .add( this.$( '.reverse-column .column-header input' ).val( this.filters[1] ) );\n\t },\n\t\n\t /** render the middle including unpaired and paired sections (which may be hidden) */\n\t _renderMiddle : function( speed, callback ){\n\t var $middle = this.$( '.middle' ).empty().html( PairedCollectionCreator.templates.middle() );\n\t\n\t // (re-) hide the un/paired panels based on instance vars\n\t if( this.unpairedPanelHidden ){\n\t this.$( '.unpaired-columns' ).hide();\n\t } else if( this.pairedPanelHidden ){\n\t this.$( '.paired-columns' ).hide();\n\t }\n\t\n\t this._renderUnpaired();\n\t this._renderPaired();\n\t return $middle;\n\t },\n\t /** render the unpaired section, showing datasets accrd. to filters, update the unpaired counts */\n\t _renderUnpaired : function( speed, callback ){\n\t //this.debug( '-- _renderUnpaired' );\n\t var creator = this,\n\t $fwd, $rev, $prd = [],\n\t split = this._splitByFilters();\n\t // update unpaired counts\n\t this.$( '.forward-column .title' )\n\t .text([ split[0].length, _l( 'unpaired forward' ) ].join( ' ' ));\n\t this.$( '.forward-column .unpaired-info' )\n\t .text( this._renderUnpairedDisplayStr( this.unpaired.length - split[0].length ) );\n\t this.$( '.reverse-column .title' )\n\t .text([ split[1].length, _l( 'unpaired reverse' ) ].join( ' ' ));\n\t this.$( '.reverse-column .unpaired-info' )\n\t .text( this._renderUnpairedDisplayStr( this.unpaired.length - split[1].length ) );\n\t\n\t this.$( '.unpaired-columns .column-datasets' ).empty();\n\t\n\t // show/hide the auto pair button if any unpaired are left\n\t this.$( '.autopair-link' ).toggle( this.unpaired.length !== 0 );\n\t if( this.unpaired.length === 0 ){\n\t this._renderUnpairedEmpty();\n\t return;\n\t }\n\t\n\t // create the dataset dom arrays\n\t $rev = split[1].map( function( dataset, i ){\n\t // if there'll be a fwd dataset across the way, add a button to pair the row\n\t if( ( split[0][ i ] !== undefined )\n\t && ( split[0][ i ] !== dataset ) ){\n\t $prd.push( creator._renderPairButton() );\n\t }\n\t return creator._renderUnpairedDataset( dataset );\n\t });\n\t $fwd = split[0].map( function( dataset ){\n\t return creator._renderUnpairedDataset( dataset );\n\t });\n\t\n\t if( !$fwd.length && !$rev.length ){\n\t this._renderUnpairedNotShown();\n\t return;\n\t }\n\t // add to appropo cols\n\t //TODO: not the best way to render - consider rendering the entire unpaired-columns section in a fragment\n\t // and swapping out that\n\t this.$( '.unpaired-columns .forward-column .column-datasets' ).append( $fwd )\n\t .add( this.$( '.unpaired-columns .paired-column .column-datasets' ).append( $prd ) )\n\t .add( this.$( '.unpaired-columns .reverse-column .column-datasets' ).append( $rev ) );\n\t this._adjUnpairedOnScrollbar();\n\t },\n\t /** return a string to display the count of filtered out datasets */\n\t _renderUnpairedDisplayStr : function( numFiltered ){\n\t return [ '(', numFiltered, ' ', _l( 'filtered out' ), ')' ].join('');\n\t },\n\t /** return an unattached jQuery DOM element to represent an unpaired dataset */\n\t _renderUnpairedDataset : function( dataset ){\n\t //TODO: to underscore template\n\t return $( '')\n\t .attr( 'id', 'dataset-' + dataset.id )\n\t .addClass( 'dataset unpaired' )\n\t .attr( 'draggable', true )\n\t .addClass( dataset.selected? 'selected': '' )\n\t .append( $( '' ).addClass( 'dataset-name' ).text( dataset.name ) )\n\t //??\n\t .data( 'dataset', dataset );\n\t },\n\t /** render the button that may go between unpaired datasets, allowing the user to pair a row */\n\t _renderPairButton : function(){\n\t //TODO: *not* a dataset - don't pretend like it is\n\t return $( '').addClass( 'dataset unpaired' )\n\t .append( $( '' ).addClass( 'dataset-name' ).text( _l( 'Pair these datasets' ) ) );\n\t },\n\t /** a message to display when no unpaired left */\n\t _renderUnpairedEmpty : function(){\n\t //this.debug( '-- renderUnpairedEmpty' );\n\t var $msg = $( '' )\n\t .text( '(' + _l( 'no remaining unpaired datasets' ) + ')' );\n\t this.$( '.unpaired-columns .paired-column .column-datasets' ).empty().prepend( $msg );\n\t return $msg;\n\t },\n\t /** a message to display when no unpaired can be shown with the current filters */\n\t _renderUnpairedNotShown : function(){\n\t //this.debug( '-- renderUnpairedEmpty' );\n\t var $msg = $( '' )\n\t .text( '(' + _l( 'no datasets were found matching the current filters' ) + ')' );\n\t this.$( '.unpaired-columns .paired-column .column-datasets' ).empty().prepend( $msg );\n\t return $msg;\n\t },\n\t /** try to detect if the unpaired section has a scrollbar and adjust left column for better centering of all */\n\t _adjUnpairedOnScrollbar : function(){\n\t var $unpairedColumns = this.$( '.unpaired-columns' ).last(),\n\t $firstDataset = this.$( '.unpaired-columns .reverse-column .dataset' ).first();\n\t if( !$firstDataset.length ){ return; }\n\t var ucRight = $unpairedColumns.offset().left + $unpairedColumns.outerWidth(),\n\t dsRight = $firstDataset.offset().left + $firstDataset.outerWidth(),\n\t rightDiff = Math.floor( ucRight ) - Math.floor( dsRight );\n\t //this.debug( 'rightDiff:', ucRight, '-', dsRight, '=', rightDiff );\n\t this.$( '.unpaired-columns .forward-column' )\n\t .css( 'margin-left', ( rightDiff > 0 )? rightDiff: 0 );\n\t },\n\t\n\t /** render the paired section and update counts of paired datasets */\n\t _renderPaired : function( speed, callback ){\n\t //this.debug( '-- _renderPaired' );\n\t this.$( '.paired-column-title .title' ).text([ this.paired.length, _l( 'paired' ) ].join( ' ' ) );\n\t // show/hide the unpair all link\n\t this.$( '.unpair-all-link' ).toggle( this.paired.length !== 0 );\n\t if( this.paired.length === 0 ){\n\t this._renderPairedEmpty();\n\t return;\n\t //TODO: would be best to return here (the $columns)\n\t } else {\n\t // show/hide 'remove extensions link' when any paired and they seem to have extensions\n\t this.$( '.remove-extensions-link' ).show();\n\t }\n\t\n\t this.$( '.paired-columns .column-datasets' ).empty();\n\t var creator = this;\n\t this.paired.forEach( function( pair, i ){\n\t //TODO: cache these?\n\t var pairView = new PairView({ pair: pair });\n\t creator.$( '.paired-columns .column-datasets' )\n\t .append( pairView.render().$el )\n\t .append([\n\t ''\n\t ].join( '' ));\n\t });\n\t },\n\t /** a message to display when none paired */\n\t _renderPairedEmpty : function(){\n\t var $msg = $( '' )\n\t .text( '(' + _l( 'no paired datasets yet' ) + ')' );\n\t this.$( '.paired-columns .column-datasets' ).empty().prepend( $msg );\n\t return $msg;\n\t },\n\t\n\t /** render the footer, completion controls, and cancel controls */\n\t _renderFooter : function( speed, callback ){\n\t var $footer = this.$( '.footer' ).empty().html( PairedCollectionCreator.templates.footer() );\n\t this.$( '.remove-extensions' ).prop( 'checked', this.removeExtensions );\n\t if( typeof this.oncancel === 'function' ){\n\t this.$( '.cancel-create.btn' ).show();\n\t }\n\t return $footer;\n\t },\n\t\n\t /** add any jQuery/bootstrap/custom plugins to elements rendered */\n\t _addPluginComponents : function(){\n\t this._chooseFiltersPopover( '.choose-filters-link' );\n\t this.$( '.help-content i' ).hoverhighlight( '.collection-creator', 'rgba( 64, 255, 255, 1.0 )' );\n\t },\n\t\n\t /** build a filter selection popover allowing selection of common filter pairs */\n\t _chooseFiltersPopover : function( selector ){\n\t function filterChoice( val1, val2 ){\n\t return [\n\t ''\n\t ].join('');\n\t }\n\t var $popoverContent = $( _.template([\n\t '
', _l([\n\t 'Collections of paired datasets are ordered lists of dataset pairs (often forward and reverse reads). ',\n\t 'These collections can be passed to tools and workflows in order to have analyses done on each member of ',\n\t 'the entire group. This interface allows you to create a collection, choose which datasets are paired, ',\n\t 'and re-order the final collection.'\n\t ].join( '' )), '
',\n\t '', _l([\n\t 'Unpaired datasets are shown in the unpaired section ',\n\t '(hover over the underlined words to highlight below). ',\n\t 'Paired datasets are shown in the paired section.',\n\t '
', _l([\n\t '
', _l([\n\t 'To unpair individual dataset pairs, click the ',\n\t 'unpair buttons ( ). ',\n\t 'Click the \"Unpair all\" link to unpair all pairs.'\n\t ].join( '' )), '
',\n\t '', _l([\n\t 'You can include or remove the file extensions (e.g. \".fastq\") from your pair names by toggling the ',\n\t '\"Remove file extensions from pair names?\" control.'\n\t ].join( '' )), '
',\n\t '', _l([\n\t 'Once your collection is complete, enter a name and ',\n\t 'click \"Create list\". ',\n\t '(Note: you do not have to pair all unpaired datasets to finish.)'\n\t ].join( '' )), '
'\n\t ].join(''))\n\t};\n\t\n\t\n\t//=============================================================================\n\t/** a modal version of the paired collection creator */\n\tvar pairedCollectionCreatorModal = function _pairedCollectionCreatorModal( datasets, options ){\n\t\n\t var deferred = jQuery.Deferred(),\n\t creator;\n\t\n\t options = _.defaults( options || {}, {\n\t datasets : datasets,\n\t oncancel : function(){\n\t Galaxy.modal.hide();\n\t deferred.reject( 'cancelled' );\n\t },\n\t oncreate : function( creator, response ){\n\t Galaxy.modal.hide();\n\t deferred.resolve( response );\n\t }\n\t });\n\t\n\t if( !window.Galaxy || !Galaxy.modal ){\n\t throw new Error( 'Galaxy or Galaxy.modal not found' );\n\t }\n\t\n\t creator = new PairedCollectionCreator( options );\n\t Galaxy.modal.show({\n\t title : 'Create a collection of paired datasets',\n\t body : creator.$el,\n\t width : '80%',\n\t height : '800px',\n\t closing_events: true\n\t });\n\t creator.render();\n\t window.creator = creator;\n\t\n\t //TODO: remove modal header\n\t return deferred;\n\t};\n\t\n\t\n\t//=============================================================================\n\tfunction createListOfPairsCollection( collection ){\n\t var elements = collection.toJSON();\n\t//TODO: validate elements\n\t return pairedCollectionCreatorModal( elements, {\n\t historyId : collection.historyId\n\t });\n\t}\n\t\n\t\n\t//=============================================================================\n\t return {\n\t PairedCollectionCreator : PairedCollectionCreator,\n\t pairedCollectionCreatorModal : pairedCollectionCreatorModal,\n\t createListOfPairsCollection : createListOfPairsCollection\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1), __webpack_require__(1)))\n\n/***/ },\n/* 109 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, jQuery) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(31),\n\t __webpack_require__(39),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( LIST_CREATOR, HDCA, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'collections';\n\t/*==============================================================================\n\tTODO:\n\t the paired creator doesn't really mesh with the list creator as parent\n\t it may be better to make an abstract super class for both\n\t composites may inherit from this (or vis-versa)\n\t PairedDatasetCollectionElementView doesn't make a lot of sense\n\t\n\t==============================================================================*/\n\t/** */\n\tvar PairedDatasetCollectionElementView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n\t _logNamespace : logNamespace,\n\t\n\t//TODO: use proper class (DatasetDCE or NestedDCDCE (or the union of both))\n\t tagName : 'li',\n\t className : 'collection-element',\n\t\n\t initialize : function( attributes ){\n\t this.element = attributes.element || {};\n\t this.identifier = attributes.identifier;\n\t },\n\t\n\t render : function(){\n\t this.$el\n\t .attr( 'data-element-id', this.element.id )\n\t .html( this.template({ identifier: this.identifier, element: this.element }) );\n\t return this;\n\t },\n\t\n\t //TODO: lots of unused space in the element - possibly load details and display them horiz.\n\t template : _.template([\n\t '<%- identifier %>',\n\t '<%- element.name %>',\n\t ].join('')),\n\t\n\t /** remove the DOM and any listeners */\n\t destroy : function(){\n\t this.off();\n\t this.$el.remove();\n\t },\n\t\n\t /** string rep */\n\t toString : function(){\n\t return 'DatasetCollectionElementView()';\n\t }\n\t});\n\t\n\t\n\t// ============================================================================\n\tvar _super = LIST_CREATOR.ListCollectionCreator;\n\t\n\t/** An interface for building collections.\n\t */\n\tvar PairCollectionCreator = _super.extend({\n\t\n\t /** the class used to display individual elements */\n\t elementViewClass : PairedDatasetCollectionElementView,\n\t /** the class this creator will create and save */\n\t collectionClass : HDCA.HistoryPairDatasetCollection,\n\t className : 'pair-collection-creator collection-creator flex-row-container',\n\t\n\t /** override to no-op */\n\t _mangleDuplicateNames : function(){},\n\t\n\t // TODO: this whole pattern sucks. There needs to be two classes of problem area:\n\t // bad inital choices and\n\t // when the user has painted his/her self into a corner during creation/use-of-the-creator\n\t /** render the entire interface */\n\t render : function( speed, callback ){\n\t if( this.workingElements.length === 2 ){\n\t return _super.prototype.render.call( this, speed, callback );\n\t }\n\t return this._renderInvalid( speed, callback );\n\t },\n\t\n\t // ------------------------------------------------------------------------ rendering elements\n\t /** render forward/reverse */\n\t _renderList : function( speed, callback ){\n\t //this.debug( '-- _renderList' );\n\t //precondition: there are two valid elements in workingElements\n\t var creator = this,\n\t $tmp = jQuery( '' ),\n\t $list = creator.$list();\n\t\n\t // lose the original views, create the new, append all at once, then call their renders\n\t _.each( this.elementViews, function( view ){\n\t view.destroy();\n\t creator.removeElementView( view );\n\t });\n\t $tmp.append( creator._createForwardElementView().$el );\n\t $tmp.append( creator._createReverseElementView().$el );\n\t $list.empty().append( $tmp.children() );\n\t _.invoke( creator.elementViews, 'render' );\n\t },\n\t\n\t /** create the forward element view */\n\t _createForwardElementView : function(){\n\t return this._createElementView( this.workingElements[0], { identifier: 'forward' } );\n\t },\n\t\n\t /** create the forward element view */\n\t _createReverseElementView : function(){\n\t return this._createElementView( this.workingElements[1], { identifier: 'reverse' } );\n\t },\n\t\n\t /** create an element view, cache in elementViews, and return */\n\t _createElementView : function( element, options ){\n\t var elementView = new this.elementViewClass( _.extend( options, {\n\t element : element,\n\t }));\n\t this.elementViews.push( elementView );\n\t return elementView;\n\t },\n\t\n\t /** swap the forward, reverse elements and re-render */\n\t swap : function(){\n\t this.workingElements = [\n\t this.workingElements[1],\n\t this.workingElements[0],\n\t ];\n\t this._renderList();\n\t },\n\t\n\t events : _.extend( _.clone( _super.prototype.events ), {\n\t 'click .swap' : 'swap',\n\t }),\n\t\n\t // ------------------------------------------------------------------------ templates\n\t //TODO: move to require text plugin and load these as text\n\t //TODO: underscore currently unnecc. bc no vars are used\n\t //TODO: better way of localizing text-nodes in long strings\n\t /** underscore template fns attached to class */\n\t templates : _.extend( _.clone( _super.prototype.templates ), {\n\t /** the middle: element list */\n\t middle : _.template([\n\t '', _l([\n\t 'Pair collections are permanent collections containing two datasets: one forward and one reverse. ',\n\t 'Often these are forward and reverse reads. The pair collections can be passed to tools and ',\n\t 'workflows in order to have analyses done on both datasets. This interface allows ',\n\t 'you to create a pair, name it, and swap which is forward and which reverse.'\n\t ].join( '' )), '
',\n\t '', _l([\n\t 'Once your collection is complete, enter a name and ',\n\t 'click \"Create list\".'\n\t ].join( '' )), '
'\n\t ].join('')),\n\t\n\t /** a simplified page communicating what went wrong and why the user needs to reselect something else */\n\t invalidInitial : _.template([\n\t '' + this.progressive + '...
';\n\t this.modal.$( '.modal-body' ).empty().append( indicator ).css({ 'margin-top': '8px' });\n\t },\n\t\n\t // (sorta) public interface - display the modal, render the form, and potentially copy the history\n\t // returns a jQuery.Deferred done->history copied, fail->user cancelled\n\t dialog : function _dialog( modal, history, options ){\n\t options = options || {};\n\t\n\t var dialog = this,\n\t deferred = jQuery.Deferred(),\n\t // TODO: getting a little byzantine here\n\t defaultCopyNameFn = options.nameFn || this.defaultName,\n\t defaultCopyName = defaultCopyNameFn({ name: history.get( 'name' ) }),\n\t // TODO: these two might be simpler as one 3 state option (all,active,no-choice)\n\t defaultCopyWhat = options.allDatasets? 'copy-all' : 'copy-non-deleted',\n\t allowAll = !_.isUndefined( options.allowAll )? options.allowAll : true,\n\t autoClose = !_.isUndefined( options.autoClose )? options.autoClose : true;\n\t\n\t this.modal = modal;\n\t\n\t\n\t // validate the name and copy if good\n\t function checkNameAndCopy(){\n\t var name = modal.$( '#copy-modal-title' ).val();\n\t if( !name ){\n\t modal.$( '.invalid-title' ).show();\n\t return;\n\t }\n\t // get further settings, shut down and indicate the ajax call, then hide and resolve/reject\n\t var copyAllDatasets = modal.$( 'input[name=\"copy-what\"]:checked' ).val() === 'copy-all';\n\t modal.$( 'button' ).prop( 'disabled', true );\n\t dialog._showAjaxIndicator();\n\t history.copy( true, name, copyAllDatasets )\n\t .done( function( response ){\n\t deferred.resolve( response );\n\t })\n\t .fail( function( xhr, status, message ){\n\t var options = { name: name, copyAllDatasets: copyAllDatasets };\n\t ERROR_MODAL.ajaxErrorModal( history, xhr, options, dialog.errorMessage );\n\t deferred.rejectWith( deferred, arguments );\n\t })\n\t .done( function(){\n\t if( autoClose ){ modal.hide(); }\n\t });\n\t }\n\t\n\t var originalClosingCallback = options.closing_callback;\n\t modal.show( _.extend( options, {\n\t title : this.title({ name: history.get( 'name' ) }),\n\t body : $( dialog._template({\n\t name : defaultCopyName,\n\t isAnon : Galaxy.user.isAnonymous(),\n\t allowAll : allowAll,\n\t copyWhat : defaultCopyWhat,\n\t activeLabel : this.activeLabel,\n\t allLabel : this.allLabel,\n\t anonWarning : this.anonWarning,\n\t })),\n\t buttons : _.object([\n\t [ _l( 'Cancel' ), function(){ modal.hide(); } ],\n\t [ this.submitLabel, checkNameAndCopy ]\n\t ]),\n\t height : 'auto',\n\t closing_events : true,\n\t closing_callback: function _historyCopyClose( cancelled ){\n\t if( cancelled ){\n\t deferred.reject({ cancelled : true });\n\t }\n\t if( originalClosingCallback ){\n\t originalClosingCallback( cancelled );\n\t }\n\t }\n\t }));\n\t\n\t // set the default dataset copy, autofocus the title, and set up for a simple return\n\t modal.$( '#copy-modal-title' ).focus().select();\n\t modal.$( '#copy-modal-title' ).on( 'keydown', function( ev ){\n\t if( ev.keyCode === 13 ){\n\t ev.preventDefault();\n\t checkNameAndCopy();\n\t }\n\t });\n\t\n\t return deferred;\n\t },\n\t};\n\t\n\t//==============================================================================\n\t// maintain the (slight) distinction between copy and import\n\t/**\n\t * Subclass CopyDialog to use the import language.\n\t */\n\tvar ImportDialog = _.extend( {}, CopyDialog, {\n\t defaultName : _.template( \"imported: <%- name %>\" ),\n\t title : _.template( _l( 'Importing history' ) + ' \"<%- name %>\"' ),\n\t submitLabel : _l( 'Import' ),\n\t errorMessage : _l( 'History could not be imported.' ),\n\t progressive : _l( 'Importing history' ),\n\t activeLabel : _l( 'Import only the active, non-deleted datasets' ),\n\t allLabel : _l( 'Import all datasets including deleted ones' ),\n\t anonWarning : _l( 'As an anonymous user, unless you login or register, you will lose your current history ' ) +\n\t _l( 'after importing this history. ' ),\n\t\n\t});\n\t\n\t//==============================================================================\n\t/**\n\t * Main interface for both history import and history copy dialogs.\n\t * @param {Backbone.Model} history the history to copy\n\t * @param {Object} options a hash\n\t * @return {jQuery.Deferred} promise that fails on close and succeeds on copy\n\t *\n\t * options:\n\t * (this object is also passed to the modal used to display the dialog and accepts modal options)\n\t * {Function} nameFn if defined, use this to build the default name shown to the user\n\t * (the fn is passed: {name:| ' +\n\t ' | ' +\n\t ' | Description | ' +\n\t 'Name | ' +\n\t 'Size | ' +\n\t 'Settings | ' +\n\t 'Status | ' +\n\t '
|---|
| Name | ' +\n\t 'Size | ' +\n\t 'Type | ' +\n\t 'Genome | ' +\n\t 'Settings | ' +\n\t 'Status | ' +\n\t '' +\n\t ' |
|---|
\" + this.formattedReference() + \"
\" );\n return this;\n },\n formattedReference: function() {\n var model = this.model;\n var entryType = model.entryType();\n var fields = model.fields();\n\n var ref = \"\";\n // Code inspired by...\n // https://github.com/vkaravir/bib-publication-list/blob/master/src/bib-publication-list.js\n var authorsAndYear = this._asSentence( (fields.author ? fields.author : \"\") + (fields.year ? (\" (\" + fields.year + \")\") : \"\") ) + \" \";\n var title = fields.title || \"\";\n var pages = fields.pages ? (\"pp. \" + fields.pages) : \"\";\n var address = fields.address;\n if( entryType == \"article\" ) {\n var volume = (fields.volume ? fields.volume : \"\") +\n (fields.number ? ( \" (\" + fields.number + \")\" ) : \"\") +\n (pages ? \", \" + pages : \"\");\n ref = authorsAndYear + this._asSentence(title) +\n (fields.journal ? (\"In \" + fields.journal + \", \") : \"\") +\n this._asSentence(volume) + \n this._asSentence(fields.address) +\n \"<\\/em>\";\n } else if( entryType == \"inproceedings\" || entryType == \"proceedings\" ) {\n ref = authorsAndYear + \n this._asSentence(title) + \n (fields.booktitle ? (\"In \" + fields.booktitle + \", \") : \"\") +\n (pages ? pages : \"\") +\n (address ? \", \" + address : \"\") + \n \".<\\/em>\";\n } else if( entryType == \"mastersthesis\" || entryType == \"phdthesis\" ) {\n ref = authorsAndYear + this._asSentence(title) +\n (fields.howpublished ? fields.howpublished + \". \" : \"\") +\n (fields.note ? fields.note + \".\" : \"\");\n } else if( entryType == \"techreport\" ) {\n ref = authorsAndYear + this._asSentence(title) +\n this._asSentence(fields.institution) +\n this._asSentence(fields.number) +\n this._asSentence(fields.type);\n } else if( entryType == \"book\" || entryType == \"inbook\" || entryType == \"incollection\" ) {\n ref = authorsAndYear + \" \" + this._formatBookInfo(fields);\n } else {\n ref = authorsAndYear + \" \" + this._asSentence(title) +\n this._asSentence(fields.howpublished) +\n this._asSentence(fields.note);\n }\n var doiUrl = \"\";\n if( fields.doi ) {\n doiUrl = 'http://dx.doi.org/' + fields.doi;\n ref += '[doi:' + fields.doi + \"]\";\n }\n var url = fields.url || doiUrl;\n if( url ) {\n ref += '[Link]';\n }\n return ref;\n },\n _formatBookInfo: function(fields) {\n var info = \"\";\n if( fields.chapter ) {\n info += fields.chapter + \" in \";\n }\n if( fields.title ) {\n info += \"\" + fields.title + \"<\\/em>\";\n }\n if( fields.editor ) {\n info += \", Edited by \" + fields.editor + \", \";\n }\n if( fields.publisher) {\n info += \", \" + fields.publisher;\n }\n if( fields.pages ) {\n info += \", pp. \" + fields.pages + \"\";\n }\n if( fields.series ) {\n info += \", \" + fields.series + \"<\\/em>\";\n }\n if( fields.volume ) {\n info += \", Vol.\" + fields.volume;\n }\n if( fields.issn ) {\n info += \", ISBN: \" + fields.issn;\n }\n return info + \".\";\n },\n _asSentence: function(str) {\n return (str && str.trim()) ? str + \". \" : \"\";\n }\n});\n\nvar CitationListView = Backbone.View.extend({\n el: '#citations',\n /**\n * Set up view.\n */\n initialize: function() {\n this.listenTo( this.collection, 'add', this.renderCitation );\n },\n\n events: {\n 'click .citations-to-bibtex': 'showBibtex',\n 'click .citations-to-formatted': 'showFormatted'\n },\n\n renderCitation: function( citation ) {\n var citationView = new CitationView( { model: citation } );\n this.$(\".citations-formatted\").append( citationView.render().el );\n var rawTextarea = this.$(\".citations-bibtex-text\");\n rawTextarea.val( rawTextarea.val() + \"\\n\\r\" + citation.attributes.content );\n },\n\n render: function() {\n this.$el.html(this.citationsElement());\n this.collection.each(function( item ){\n this.renderCitation( item );\n }, this);\n this.showFormatted();\n },\n\n showBibtex: function() {\n this.$(\".citations-to-formatted\").show();\n this.$(\".citations-to-bibtex\").hide();\n this.$(\".citations-bibtex\").show();\n this.$(\".citations-formatted\").hide();\n this.$(\".citations-bibtex-text\").select();\n },\n\n showFormatted: function() {\n this.$(\".citations-to-formatted\").hide();\n this.$(\".citations-to-bibtex\").show();\n this.$(\".citations-bibtex\").hide();\n this.$(\".citations-formatted\").show();\n },\n\n partialWarningElement: function() {\n if( this.collection.partial ) {\n return [\n '' + JSON.stringify( xhr.responseJSON ) + '';\n } else {\n content += ': ' + message;\n }\n }\n creator._showAlert( content, 'alert-danger' );\n },\n\n events : {\n // header\n 'click .more-help' : '_clickMoreHelp',\n 'click .less-help' : '_clickLessHelp',\n 'click .main-help' : '_toggleHelp',\n 'click .header .alert button' : '_hideAlert',\n\n 'click .reset' : 'reset',\n 'click .clear-selected' : 'clearSelectedElements',\n\n // elements - selection\n 'click .collection-elements' : 'clearSelectedElements',\n\n // elements - drop target\n // 'dragenter .collection-elements': '_dragenterElements',\n // 'dragleave .collection-elements': '_dragleaveElements',\n 'dragover .collection-elements' : '_dragoverElements',\n 'drop .collection-elements' : '_dropElements',\n\n // these bubble up from the elements as custom events\n 'collection-element.dragstart .collection-elements' : '_elementDragstart',\n 'collection-element.dragend .collection-elements' : '_elementDragend',\n\n // footer\n 'change .collection-name' : '_changeName',\n 'keydown .collection-name' : '_nameCheckForEnter',\n 'click .cancel-create' : function( ev ){\n if( typeof this.oncancel === 'function' ){\n this.oncancel.call( this );\n }\n },\n 'click .create-collection' : '_clickCreate'//,\n },\n\n // ........................................................................ header\n /** expand help */\n _clickMoreHelp : function( ev ){\n ev.stopPropagation();\n this.$( '.main-help' ).addClass( 'expanded' );\n this.$( '.more-help' ).hide();\n },\n /** collapse help */\n _clickLessHelp : function( ev ){\n ev.stopPropagation();\n this.$( '.main-help' ).removeClass( 'expanded' );\n this.$( '.more-help' ).show();\n },\n /** toggle help */\n _toggleHelp : function( ev ){\n ev.stopPropagation();\n this.$( '.main-help' ).toggleClass( 'expanded' );\n this.$( '.more-help' ).toggle();\n },\n\n /** show an alert on the top of the interface containing message (alertClass is bootstrap's alert-*) */\n _showAlert : function( message, alertClass ){\n alertClass = alertClass || 'alert-danger';\n this.$( '.main-help' ).hide();\n this.$( '.header .alert' )\n .attr( 'class', 'alert alert-dismissable' ).addClass( alertClass ).show()\n .find( '.alert-message' ).html( message );\n },\n /** hide the alerts at the top */\n _hideAlert : function( message ){\n this.$( '.main-help' ).show();\n this.$( '.header .alert' ).hide();\n },\n\n // ........................................................................ elements\n /** reset all data to the initial state */\n reset : function(){\n this._instanceSetUp();\n this._elementsSetUp();\n this.render();\n },\n\n /** deselect all elements */\n clearSelectedElements : function( ev ){\n this.$( '.collection-elements .collection-element' ).removeClass( 'selected' );\n this.$( '.collection-elements-controls > .clear-selected' ).hide();\n },\n\n //_dragenterElements : function( ev ){\n // //this.debug( '_dragenterElements:', ev );\n //},\n//TODO: if selected are dragged out of the list area - remove the placeholder - cuz it won't work anyway\n // _dragleaveElements : function( ev ){\n // //this.debug( '_dragleaveElements:', ev );\n // },\n\n /** track the mouse drag over the list adding a placeholder to show where the drop would occur */\n _dragoverElements : function( ev ){\n //this.debug( '_dragoverElements:', ev );\n ev.preventDefault();\n\n var $list = this.$list();\n this._checkForAutoscroll( $list, ev.originalEvent.clientY );\n var $nearest = this._getNearestElement( ev.originalEvent.clientY );\n\n //TODO: no need to re-create - move instead\n this.$( '.element-drop-placeholder' ).remove();\n var $placeholder = $( '' );\n if( !$nearest.length ){\n $list.append( $placeholder );\n } else {\n $nearest.before( $placeholder );\n }\n },\n\n /** If the mouse is near enough to the list's top or bottom, scroll the list */\n _checkForAutoscroll : function( $element, y ){\n var AUTOSCROLL_SPEED = 2,\n offset = $element.offset(),\n scrollTop = $element.scrollTop(),\n upperDist = y - offset.top,\n lowerDist = ( offset.top + $element.outerHeight() ) - y;\n if( upperDist >= 0 && upperDist < this.autoscrollDist ){\n $element.scrollTop( scrollTop - AUTOSCROLL_SPEED );\n } else if( lowerDist >= 0 && lowerDist < this.autoscrollDist ){\n $element.scrollTop( scrollTop + AUTOSCROLL_SPEED );\n }\n },\n\n /** get the nearest element based on the mouse's Y coordinate.\n * If the y is at the end of the list, return an empty jQuery object.\n */\n _getNearestElement : function( y ){\n var WIGGLE = 4,\n lis = this.$( '.collection-elements li.collection-element' ).toArray();\n for( var i=0; i
', _l([\n 'Collections of datasets are permanent, ordered lists of datasets that can be passed to tools and ',\n 'workflows in order to have analyses done on each member of the entire group. This interface allows ',\n 'you to create a collection and re-order the final collection.'\n ].join( '' )), '
',\n '', _l([\n 'Once your collection is complete, enter a name and ',\n 'click \"Create list\".'\n ].join( '' )), '
'\n ].join('')),\n\n /** shown in list when all elements are discarded */\n invalidElements : _.template([\n _l( 'The following selections could not be included due to problems:' ),\n '<%= dataset.peek %>',\n '<% } %>',\n '<% } %>',\n '
| ' +\n ' | Name | ' +\n 'Size | ' +\n 'Created | ' +\n '
|---|
' + JSON.stringify( xhr.responseJSON ) + '';\n } else {\n content += ': ' + message;\n }\n }\n creator._showAlert( content, 'alert-danger' );\n },\n\n // ------------------------------------------------------------------------ rendering\n /** render the entire interface */\n render : function( speed, callback ){\n //this.debug( '-- _render' );\n //this.$el.empty().html( PairedCollectionCreator.templates.main() );\n this.$el.empty().html( PairedCollectionCreator.templates.main() );\n this._renderHeader( speed );\n this._renderMiddle( speed );\n this._renderFooter( speed );\n this._addPluginComponents();\n this.trigger( 'rendered', this );\n return this;\n },\n\n /** render the header section */\n _renderHeader : function( speed, callback ){\n //this.debug( '-- _renderHeader' );\n var $header = this.$( '.header' ).empty().html( PairedCollectionCreator.templates.header() )\n .find( '.help-content' ).prepend( $( PairedCollectionCreator.templates.helpContent() ) );\n\n this._renderFilters();\n return $header;\n },\n /** fill the filter inputs with the filter values */\n _renderFilters : function(){\n return this.$( '.forward-column .column-header input' ).val( this.filters[0] )\n .add( this.$( '.reverse-column .column-header input' ).val( this.filters[1] ) );\n },\n\n /** render the middle including unpaired and paired sections (which may be hidden) */\n _renderMiddle : function( speed, callback ){\n var $middle = this.$( '.middle' ).empty().html( PairedCollectionCreator.templates.middle() );\n\n // (re-) hide the un/paired panels based on instance vars\n if( this.unpairedPanelHidden ){\n this.$( '.unpaired-columns' ).hide();\n } else if( this.pairedPanelHidden ){\n this.$( '.paired-columns' ).hide();\n }\n\n this._renderUnpaired();\n this._renderPaired();\n return $middle;\n },\n /** render the unpaired section, showing datasets accrd. to filters, update the unpaired counts */\n _renderUnpaired : function( speed, callback ){\n //this.debug( '-- _renderUnpaired' );\n var creator = this,\n $fwd, $rev, $prd = [],\n split = this._splitByFilters();\n // update unpaired counts\n this.$( '.forward-column .title' )\n .text([ split[0].length, _l( 'unpaired forward' ) ].join( ' ' ));\n this.$( '.forward-column .unpaired-info' )\n .text( this._renderUnpairedDisplayStr( this.unpaired.length - split[0].length ) );\n this.$( '.reverse-column .title' )\n .text([ split[1].length, _l( 'unpaired reverse' ) ].join( ' ' ));\n this.$( '.reverse-column .unpaired-info' )\n .text( this._renderUnpairedDisplayStr( this.unpaired.length - split[1].length ) );\n\n this.$( '.unpaired-columns .column-datasets' ).empty();\n\n // show/hide the auto pair button if any unpaired are left\n this.$( '.autopair-link' ).toggle( this.unpaired.length !== 0 );\n if( this.unpaired.length === 0 ){\n this._renderUnpairedEmpty();\n return;\n }\n\n // create the dataset dom arrays\n $rev = split[1].map( function( dataset, i ){\n // if there'll be a fwd dataset across the way, add a button to pair the row\n if( ( split[0][ i ] !== undefined )\n && ( split[0][ i ] !== dataset ) ){\n $prd.push( creator._renderPairButton() );\n }\n return creator._renderUnpairedDataset( dataset );\n });\n $fwd = split[0].map( function( dataset ){\n return creator._renderUnpairedDataset( dataset );\n });\n\n if( !$fwd.length && !$rev.length ){\n this._renderUnpairedNotShown();\n return;\n }\n // add to appropo cols\n //TODO: not the best way to render - consider rendering the entire unpaired-columns section in a fragment\n // and swapping out that\n this.$( '.unpaired-columns .forward-column .column-datasets' ).append( $fwd )\n .add( this.$( '.unpaired-columns .paired-column .column-datasets' ).append( $prd ) )\n .add( this.$( '.unpaired-columns .reverse-column .column-datasets' ).append( $rev ) );\n this._adjUnpairedOnScrollbar();\n },\n /** return a string to display the count of filtered out datasets */\n _renderUnpairedDisplayStr : function( numFiltered ){\n return [ '(', numFiltered, ' ', _l( 'filtered out' ), ')' ].join('');\n },\n /** return an unattached jQuery DOM element to represent an unpaired dataset */\n _renderUnpairedDataset : function( dataset ){\n //TODO: to underscore template\n return $( '')\n .attr( 'id', 'dataset-' + dataset.id )\n .addClass( 'dataset unpaired' )\n .attr( 'draggable', true )\n .addClass( dataset.selected? 'selected': '' )\n .append( $( '' ).addClass( 'dataset-name' ).text( dataset.name ) )\n //??\n .data( 'dataset', dataset );\n },\n /** render the button that may go between unpaired datasets, allowing the user to pair a row */\n _renderPairButton : function(){\n //TODO: *not* a dataset - don't pretend like it is\n return $( '').addClass( 'dataset unpaired' )\n .append( $( '' ).addClass( 'dataset-name' ).text( _l( 'Pair these datasets' ) ) );\n },\n /** a message to display when no unpaired left */\n _renderUnpairedEmpty : function(){\n //this.debug( '-- renderUnpairedEmpty' );\n var $msg = $( '' )\n .text( '(' + _l( 'no remaining unpaired datasets' ) + ')' );\n this.$( '.unpaired-columns .paired-column .column-datasets' ).empty().prepend( $msg );\n return $msg;\n },\n /** a message to display when no unpaired can be shown with the current filters */\n _renderUnpairedNotShown : function(){\n //this.debug( '-- renderUnpairedEmpty' );\n var $msg = $( '' )\n .text( '(' + _l( 'no datasets were found matching the current filters' ) + ')' );\n this.$( '.unpaired-columns .paired-column .column-datasets' ).empty().prepend( $msg );\n return $msg;\n },\n /** try to detect if the unpaired section has a scrollbar and adjust left column for better centering of all */\n _adjUnpairedOnScrollbar : function(){\n var $unpairedColumns = this.$( '.unpaired-columns' ).last(),\n $firstDataset = this.$( '.unpaired-columns .reverse-column .dataset' ).first();\n if( !$firstDataset.length ){ return; }\n var ucRight = $unpairedColumns.offset().left + $unpairedColumns.outerWidth(),\n dsRight = $firstDataset.offset().left + $firstDataset.outerWidth(),\n rightDiff = Math.floor( ucRight ) - Math.floor( dsRight );\n //this.debug( 'rightDiff:', ucRight, '-', dsRight, '=', rightDiff );\n this.$( '.unpaired-columns .forward-column' )\n .css( 'margin-left', ( rightDiff > 0 )? rightDiff: 0 );\n },\n\n /** render the paired section and update counts of paired datasets */\n _renderPaired : function( speed, callback ){\n //this.debug( '-- _renderPaired' );\n this.$( '.paired-column-title .title' ).text([ this.paired.length, _l( 'paired' ) ].join( ' ' ) );\n // show/hide the unpair all link\n this.$( '.unpair-all-link' ).toggle( this.paired.length !== 0 );\n if( this.paired.length === 0 ){\n this._renderPairedEmpty();\n return;\n //TODO: would be best to return here (the $columns)\n } else {\n // show/hide 'remove extensions link' when any paired and they seem to have extensions\n this.$( '.remove-extensions-link' ).show();\n }\n\n this.$( '.paired-columns .column-datasets' ).empty();\n var creator = this;\n this.paired.forEach( function( pair, i ){\n //TODO: cache these?\n var pairView = new PairView({ pair: pair });\n creator.$( '.paired-columns .column-datasets' )\n .append( pairView.render().$el )\n .append([\n ''\n ].join( '' ));\n });\n },\n /** a message to display when none paired */\n _renderPairedEmpty : function(){\n var $msg = $( '' )\n .text( '(' + _l( 'no paired datasets yet' ) + ')' );\n this.$( '.paired-columns .column-datasets' ).empty().prepend( $msg );\n return $msg;\n },\n\n /** render the footer, completion controls, and cancel controls */\n _renderFooter : function( speed, callback ){\n var $footer = this.$( '.footer' ).empty().html( PairedCollectionCreator.templates.footer() );\n this.$( '.remove-extensions' ).prop( 'checked', this.removeExtensions );\n if( typeof this.oncancel === 'function' ){\n this.$( '.cancel-create.btn' ).show();\n }\n return $footer;\n },\n\n /** add any jQuery/bootstrap/custom plugins to elements rendered */\n _addPluginComponents : function(){\n this._chooseFiltersPopover( '.choose-filters-link' );\n this.$( '.help-content i' ).hoverhighlight( '.collection-creator', 'rgba( 64, 255, 255, 1.0 )' );\n },\n\n /** build a filter selection popover allowing selection of common filter pairs */\n _chooseFiltersPopover : function( selector ){\n function filterChoice( val1, val2 ){\n return [\n ''\n ].join('');\n }\n var $popoverContent = $( _.template([\n '
', _l([\n 'Collections of paired datasets are ordered lists of dataset pairs (often forward and reverse reads). ',\n 'These collections can be passed to tools and workflows in order to have analyses done on each member of ',\n 'the entire group. This interface allows you to create a collection, choose which datasets are paired, ',\n 'and re-order the final collection.'\n ].join( '' )), '
',\n '', _l([\n 'Unpaired datasets are shown in the unpaired section ',\n '(hover over the underlined words to highlight below). ',\n 'Paired datasets are shown in the paired section.',\n '
', _l([\n '
', _l([\n 'To unpair individual dataset pairs, click the ',\n 'unpair buttons ( ). ',\n 'Click the \"Unpair all\" link to unpair all pairs.'\n ].join( '' )), '
',\n '', _l([\n 'You can include or remove the file extensions (e.g. \".fastq\") from your pair names by toggling the ',\n '\"Remove file extensions from pair names?\" control.'\n ].join( '' )), '
',\n '', _l([\n 'Once your collection is complete, enter a name and ',\n 'click \"Create list\". ',\n '(Note: you do not have to pair all unpaired datasets to finish.)'\n ].join( '' )), '
'\n ].join(''))\n};\n\n\n//=============================================================================\n/** a modal version of the paired collection creator */\nvar pairedCollectionCreatorModal = function _pairedCollectionCreatorModal( datasets, options ){\n\n var deferred = jQuery.Deferred(),\n creator;\n\n options = _.defaults( options || {}, {\n datasets : datasets,\n oncancel : function(){\n Galaxy.modal.hide();\n deferred.reject( 'cancelled' );\n },\n oncreate : function( creator, response ){\n Galaxy.modal.hide();\n deferred.resolve( response );\n }\n });\n\n if( !window.Galaxy || !Galaxy.modal ){\n throw new Error( 'Galaxy or Galaxy.modal not found' );\n }\n\n creator = new PairedCollectionCreator( options );\n Galaxy.modal.show({\n title : 'Create a collection of paired datasets',\n body : creator.$el,\n width : '80%',\n height : '800px',\n closing_events: true\n });\n creator.render();\n window.creator = creator;\n\n //TODO: remove modal header\n return deferred;\n};\n\n\n//=============================================================================\nfunction createListOfPairsCollection( collection ){\n var elements = collection.toJSON();\n//TODO: validate elements\n return pairedCollectionCreatorModal( elements, {\n historyId : collection.historyId\n });\n}\n\n\n//=============================================================================\n return {\n PairedCollectionCreator : PairedCollectionCreator,\n pairedCollectionCreatorModal : pairedCollectionCreatorModal,\n createListOfPairsCollection : createListOfPairsCollection\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/collection/list-of-pairs-collection-creator.js\n ** module id = 108\n ** module chunks = 3\n **/","define([\n \"mvc/collection/list-collection-creator\",\n \"mvc/history/hdca-model\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( LIST_CREATOR, HDCA, BASE_MVC, _l ){\n\n'use strict';\n\nvar logNamespace = 'collections';\n/*==============================================================================\nTODO:\n the paired creator doesn't really mesh with the list creator as parent\n it may be better to make an abstract super class for both\n composites may inherit from this (or vis-versa)\n PairedDatasetCollectionElementView doesn't make a lot of sense\n\n==============================================================================*/\n/** */\nvar PairedDatasetCollectionElementView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n _logNamespace : logNamespace,\n\n//TODO: use proper class (DatasetDCE or NestedDCDCE (or the union of both))\n tagName : 'li',\n className : 'collection-element',\n\n initialize : function( attributes ){\n this.element = attributes.element || {};\n this.identifier = attributes.identifier;\n },\n\n render : function(){\n this.$el\n .attr( 'data-element-id', this.element.id )\n .html( this.template({ identifier: this.identifier, element: this.element }) );\n return this;\n },\n\n //TODO: lots of unused space in the element - possibly load details and display them horiz.\n template : _.template([\n '<%- identifier %>',\n '<%- element.name %>',\n ].join('')),\n\n /** remove the DOM and any listeners */\n destroy : function(){\n this.off();\n this.$el.remove();\n },\n\n /** string rep */\n toString : function(){\n return 'DatasetCollectionElementView()';\n }\n});\n\n\n// ============================================================================\nvar _super = LIST_CREATOR.ListCollectionCreator;\n\n/** An interface for building collections.\n */\nvar PairCollectionCreator = _super.extend({\n\n /** the class used to display individual elements */\n elementViewClass : PairedDatasetCollectionElementView,\n /** the class this creator will create and save */\n collectionClass : HDCA.HistoryPairDatasetCollection,\n className : 'pair-collection-creator collection-creator flex-row-container',\n\n /** override to no-op */\n _mangleDuplicateNames : function(){},\n\n // TODO: this whole pattern sucks. There needs to be two classes of problem area:\n // bad inital choices and\n // when the user has painted his/her self into a corner during creation/use-of-the-creator\n /** render the entire interface */\n render : function( speed, callback ){\n if( this.workingElements.length === 2 ){\n return _super.prototype.render.call( this, speed, callback );\n }\n return this._renderInvalid( speed, callback );\n },\n\n // ------------------------------------------------------------------------ rendering elements\n /** render forward/reverse */\n _renderList : function( speed, callback ){\n //this.debug( '-- _renderList' );\n //precondition: there are two valid elements in workingElements\n var creator = this,\n $tmp = jQuery( '' ),\n $list = creator.$list();\n\n // lose the original views, create the new, append all at once, then call their renders\n _.each( this.elementViews, function( view ){\n view.destroy();\n creator.removeElementView( view );\n });\n $tmp.append( creator._createForwardElementView().$el );\n $tmp.append( creator._createReverseElementView().$el );\n $list.empty().append( $tmp.children() );\n _.invoke( creator.elementViews, 'render' );\n },\n\n /** create the forward element view */\n _createForwardElementView : function(){\n return this._createElementView( this.workingElements[0], { identifier: 'forward' } );\n },\n\n /** create the forward element view */\n _createReverseElementView : function(){\n return this._createElementView( this.workingElements[1], { identifier: 'reverse' } );\n },\n\n /** create an element view, cache in elementViews, and return */\n _createElementView : function( element, options ){\n var elementView = new this.elementViewClass( _.extend( options, {\n element : element,\n }));\n this.elementViews.push( elementView );\n return elementView;\n },\n\n /** swap the forward, reverse elements and re-render */\n swap : function(){\n this.workingElements = [\n this.workingElements[1],\n this.workingElements[0],\n ];\n this._renderList();\n },\n\n events : _.extend( _.clone( _super.prototype.events ), {\n 'click .swap' : 'swap',\n }),\n\n // ------------------------------------------------------------------------ templates\n //TODO: move to require text plugin and load these as text\n //TODO: underscore currently unnecc. bc no vars are used\n //TODO: better way of localizing text-nodes in long strings\n /** underscore template fns attached to class */\n templates : _.extend( _.clone( _super.prototype.templates ), {\n /** the middle: element list */\n middle : _.template([\n '', _l([\n 'Pair collections are permanent collections containing two datasets: one forward and one reverse. ',\n 'Often these are forward and reverse reads. The pair collections can be passed to tools and ',\n 'workflows in order to have analyses done on both datasets. This interface allows ',\n 'you to create a pair, name it, and swap which is forward and which reverse.'\n ].join( '' )), '
',\n '', _l([\n 'Once your collection is complete, enter a name and ',\n 'click \"Create list\".'\n ].join( '' )), '
'\n ].join('')),\n\n /** a simplified page communicating what went wrong and why the user needs to reselect something else */\n invalidInitial : _.template([\n '' + this.progressive + '...
';\n this.modal.$( '.modal-body' ).empty().append( indicator ).css({ 'margin-top': '8px' });\n },\n\n // (sorta) public interface - display the modal, render the form, and potentially copy the history\n // returns a jQuery.Deferred done->history copied, fail->user cancelled\n dialog : function _dialog( modal, history, options ){\n options = options || {};\n\n var dialog = this,\n deferred = jQuery.Deferred(),\n // TODO: getting a little byzantine here\n defaultCopyNameFn = options.nameFn || this.defaultName,\n defaultCopyName = defaultCopyNameFn({ name: history.get( 'name' ) }),\n // TODO: these two might be simpler as one 3 state option (all,active,no-choice)\n defaultCopyWhat = options.allDatasets? 'copy-all' : 'copy-non-deleted',\n allowAll = !_.isUndefined( options.allowAll )? options.allowAll : true,\n autoClose = !_.isUndefined( options.autoClose )? options.autoClose : true;\n\n this.modal = modal;\n\n\n // validate the name and copy if good\n function checkNameAndCopy(){\n var name = modal.$( '#copy-modal-title' ).val();\n if( !name ){\n modal.$( '.invalid-title' ).show();\n return;\n }\n // get further settings, shut down and indicate the ajax call, then hide and resolve/reject\n var copyAllDatasets = modal.$( 'input[name=\"copy-what\"]:checked' ).val() === 'copy-all';\n modal.$( 'button' ).prop( 'disabled', true );\n dialog._showAjaxIndicator();\n history.copy( true, name, copyAllDatasets )\n .done( function( response ){\n deferred.resolve( response );\n })\n .fail( function( xhr, status, message ){\n var options = { name: name, copyAllDatasets: copyAllDatasets };\n ERROR_MODAL.ajaxErrorModal( history, xhr, options, dialog.errorMessage );\n deferred.rejectWith( deferred, arguments );\n })\n .done( function(){\n if( autoClose ){ modal.hide(); }\n });\n }\n\n var originalClosingCallback = options.closing_callback;\n modal.show( _.extend( options, {\n title : this.title({ name: history.get( 'name' ) }),\n body : $( dialog._template({\n name : defaultCopyName,\n isAnon : Galaxy.user.isAnonymous(),\n allowAll : allowAll,\n copyWhat : defaultCopyWhat,\n activeLabel : this.activeLabel,\n allLabel : this.allLabel,\n anonWarning : this.anonWarning,\n })),\n buttons : _.object([\n [ _l( 'Cancel' ), function(){ modal.hide(); } ],\n [ this.submitLabel, checkNameAndCopy ]\n ]),\n height : 'auto',\n closing_events : true,\n closing_callback: function _historyCopyClose( cancelled ){\n if( cancelled ){\n deferred.reject({ cancelled : true });\n }\n if( originalClosingCallback ){\n originalClosingCallback( cancelled );\n }\n }\n }));\n\n // set the default dataset copy, autofocus the title, and set up for a simple return\n modal.$( '#copy-modal-title' ).focus().select();\n modal.$( '#copy-modal-title' ).on( 'keydown', function( ev ){\n if( ev.keyCode === 13 ){\n ev.preventDefault();\n checkNameAndCopy();\n }\n });\n\n return deferred;\n },\n};\n\n//==============================================================================\n// maintain the (slight) distinction between copy and import\n/**\n * Subclass CopyDialog to use the import language.\n */\nvar ImportDialog = _.extend( {}, CopyDialog, {\n defaultName : _.template( \"imported: <%- name %>\" ),\n title : _.template( _l( 'Importing history' ) + ' \"<%- name %>\"' ),\n submitLabel : _l( 'Import' ),\n errorMessage : _l( 'History could not be imported.' ),\n progressive : _l( 'Importing history' ),\n activeLabel : _l( 'Import only the active, non-deleted datasets' ),\n allLabel : _l( 'Import all datasets including deleted ones' ),\n anonWarning : _l( 'As an anonymous user, unless you login or register, you will lose your current history ' ) +\n _l( 'after importing this history. ' ),\n\n});\n\n//==============================================================================\n/**\n * Main interface for both history import and history copy dialogs.\n * @param {Backbone.Model} history the history to copy\n * @param {Object} options a hash\n * @return {jQuery.Deferred} promise that fails on close and succeeds on copy\n *\n * options:\n * (this object is also passed to the modal used to display the dialog and accepts modal options)\n * {Function} nameFn if defined, use this to build the default name shown to the user\n * (the fn is passed: {name:| ' +\n ' | ' +\n ' | Description | ' +\n 'Name | ' +\n 'Size | ' +\n 'Settings | ' +\n 'Status | ' +\n '
|---|
| Name | ' +\n 'Size | ' +\n 'Type | ' +\n 'Genome | ' +\n 'Settings | ' +\n 'Status | ' +\n '' +\n ' |
|---|
${error_dict[param.name]}These datasets will appear in a new history: - - '${h.to_unicode(invocation['new_history'].name)}'. -
- %endif -${data.hid}: ${util.unicodify( data.name )}
- %endif - %endfor - %endfor -