diff --git a/client/galaxy/scripts/apps/analysis.js b/client/galaxy/scripts/apps/analysis.js index bc929786f05..9ba05169265 100644 --- a/client/galaxy/scripts/apps/analysis.js +++ b/client/galaxy/scripts/apps/analysis.js @@ -1,9 +1,88 @@ -var App = require( '../mvc/app/app-view' ); +var jQuery = require( 'jquery' ), + $ = jQuery, + PANEL = require( 'layout/panel' ), + ToolPanel = require( './tool-panel' ), + HistoryPanel = require( './history-panel' ), + PAGE = require( 'layout/page' ), + ToolsForm = require( 'mvc/tools/tools-form' ); -//TODO: this doesn't address multiple apps per page window.app = function app( options, bootstrapped ){ - $( function() { - var app = new App( options.config ); + Galaxy.debug( 'building app:', options, bootstrapped ); + + // .................................................... panels and page + var toolPanel = new ToolPanel({ + el : '#left', + userIsAnonymous : Galaxy.user.isAnonymous(), + require_login : options.config.require_login, + spinner_url : options.config.spinner_url, + search_url : options.config.search_url, + toolbox : options.config.toolbox, + toolbox_in_panel: options.config.toolbox_in_panel, + stored_workflow_menu_entries : options.config.stored_workflow_menu_entries, + }), + centerPanel = new PANEL.CenterPanel({ + el : '#center' + }), + historyPanel = new HistoryPanel({ + el : '#right', + galaxyRoot : Galaxy.root, + userIsAnonymous : Galaxy.user.isAnonymous(), + allow_user_dataset_purge: Galaxy.config.allow_user_dataset_purge, + }), + analysisPage = new PAGE.PageLayoutView( _.extend( options, { + el : 'body', + left : toolPanel, + center : centerPanel, + right : historyPanel, + })); + + // .................................................... decorate the galaxy object + // TODO: most of this is becoming unnecessary as we move to apps + Galaxy.page = analysisPage; + + // add tool panel to Galaxy object + Galaxy.toolPanel = toolPanel.tool_panel; + Galaxy.upload = toolPanel.uploadButton; + + Galaxy.currHistoryPanel = historyPanel.historyView; + Galaxy.currHistoryPanel.connectToQuotaMeter( Galaxy.quotaMeter ); + Galaxy.currHistoryPanel.listenToGalaxy( Galaxy ); + + //HACK: move to router/App + Galaxy.app = { + display : function( view, target ){ + // TODO: Remove this line after select2 update + $( '.select2-hidden-accessible' ).remove(); + centerPanel.display( view ); + }, + }; + + // .................................................... start up + $(function(){ + analysisPage.render(); + Galaxy.currHistoryPanel.loadCurrentHistory(); + + // TODO: to router, remove Globals + var params = $.extend( {}, options.config.params ); + if( params.tool_id && params.tool_id !== 'upload1' ){ + params.id = params.tool_id; + centerPanel.display( new ToolsForm.View( params ) ); + + } else if( params.job_id ){ + params.id = params.tool_id; + centerPanel.display( new ToolsForm.View( params ) ); + + } else { + var iframeUrl = Galaxy.root; + if( params.workflow_id ){ + iframeUrl += 'workflow/run?id=' + params.workflow_id; + } else if( params.m_c ){ + iframeUrl += params.m_c + '/' + params.m_a; + } else { + iframeUrl += 'root/welcome'; + } + centerPanel.$( '#galaxy_main' ).prop( 'src', iframeUrl ); + } }); }; diff --git a/client/galaxy/scripts/apps/galaxy.js b/client/galaxy/scripts/apps/galaxy.js index 3cbb69a7545..b1899f89723 100644 --- a/client/galaxy/scripts/apps/galaxy.js +++ b/client/galaxy/scripts/apps/galaxy.js @@ -1,4 +1,4 @@ +//TODO: remove this var GalaxyApp = require( '../galaxy-app-base' ).GalaxyApp; window.GalaxyApp = GalaxyApp; -console.debug( 'galaxy.js, GalaxyApp is now in window' ); exports = GalaxyApp; diff --git a/client/galaxy/scripts/apps/history-panel.js b/client/galaxy/scripts/apps/history-panel.js new file mode 100644 index 00000000000..fc0f4feed73 --- /dev/null +++ b/client/galaxy/scripts/apps/history-panel.js @@ -0,0 +1,83 @@ +var RightPanel = require( 'layout/panel' ).RightPanel, + Ui = require( 'mvc/ui/ui-misc' ), + historyOptionsMenu = require( 'mvc/history/options-menu' ); + CurrentHistoryView = require( 'mvc/history/history-panel-edit-current' ).CurrentHistoryPanel; + +//TODO: localize +// rename other {history,list}-panels to {history,list}-views +var HistoryPanel = RightPanel.extend({ + + title : 'History', + + initialize : function( options ){ + RightPanel.prototype.initialize.call( this, options ); + var self = this; + + // this button re-fetches the history and contents and re-renders the history panel + this.refreshButton = new Ui.ButtonLink({ + id : 'history-refresh-button', + title : 'Refresh history', + cls : 'panel-header-button', + icon : 'fa fa-refresh', + onclick : function() { + self.historyView.loadCurrentHistory(); + } + }); + // opens a drop down menu with history related functions (like view all, delete, share, etc.) + this.optionsButton = new Ui.ButtonLink({ + id : 'history-options-button', + title : 'History options', + cls : 'panel-header-button', + icon : 'fa fa-cog', + }); + // goes to a page showing all the users histories in panel form (for logged in users) + this.viewMultiButton = null; + if( !options.userIsAnonymous ){ + this.viewMultiButton = new Ui.ButtonLink({ + id : 'history-view-multi-button', + title : 'View all histories', + cls : 'panel-header-button', + icon : 'fa fa-columns', + href : options.galaxyRoot + 'history/view_multiple' + }); + } + + // build history options menu + this.optionsMenu = historyOptionsMenu( this.optionsButton.$el, { + anonymous : options.userIsAnonymous, + purgeAllowed : options.allow_user_dataset_purge, + root : options.galaxyRoot + }); + + // view of the current history + this.historyView = new CurrentHistoryView({ + purgeAllowed : options.allow_user_dataset_purge, + linkTarget : 'galaxy_main', + $scrollContainer: function(){ return this.$el.parent(); } + }); + }, + + render : function(){ + RightPanel.prototype.render.call( this ); + this.debug( 'rendering:' ); + this.$( '.panel-header-buttons' ).append([ + this.refreshButton.$el, + this.optionsButton.$el, + this.viewMultiButton? this.viewMultiButton.$el : null, + ]); + this.historyView.setElement( this.$( '.history-panel' ) ); + }, + + /** panel dom template. id is 'right' or 'left' */ + _templateBody : function( data ){ + return [ + '
"+JSON.stringify(i.responseJSON)+"":content+=": "+n}e._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(t){"function"==typeof this.oncancel&&this.oncancel.call(this)},"click .create-collection":"_clickCreate"},_clickMoreHelp:function(t){t.stopPropagation(),this.$(".main-help").addClass("expanded"),this.$(".more-help").hide()},_clickLessHelp:function(t){t.stopPropagation(),this.$(".main-help").removeClass("expanded"),this.$(".more-help").show()},_toggleHelp:function(t){t.stopPropagation(),this.$(".main-help").toggleClass("expanded"),this.$(".more-help").toggle()},_showAlert:function(t,e){e=e||"alert-danger",this.$(".main-help").hide(),this.$(".header .alert").attr("class","alert alert-dismissable").addClass(e).show().find(".alert-message").html(t)},_hideAlert:function(t){this.$(".main-help").show(),this.$(".header .alert").hide()},reset:function(){this._instanceSetUp(),this._elementsSetUp(),this.render()},clearSelectedElements:function(t){this.$(".collection-elements .collection-element").removeClass("selected"),this.$(".collection-elements-controls > .clear-selected").hide()},_dragoverElements:function(t){t.preventDefault();var e=this.$list();this._checkForAutoscroll(e,t.originalEvent.clientY);var i=this._getNearestElement(t.originalEvent.clientY);this.$(".element-drop-placeholder").remove();var n=r('');i.size()?i.before(n):e.append(n)},_checkForAutoscroll:function(t,e){var i=2,n=t.offset(),s=t.scrollTop(),a=e-n.top,o=n.top+t.outerHeight()-e;a>=0&&a
",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:o.template([c("The following selections could not be included due to problems:"),"<%= dataset.peek %>',"<% } %>","<% } %>","
'+i+" been successfully added to the queue - resulting in the following datasets:
";for(var s in t.outputs)n+=''+t.outputs[s].hid+": "+t.outputs[s].name+"
";return n+="You can check the status of queued jobs and view the resulting data by refreshing the History pane. When the job has been run the status will change from 'running' to 'finished' if completed successfully or 'error' if problems were encountered.
The server could not complete the request. Please contact the Galaxy Team if this error persists.
| Name | Size | Created |
|---|
| Convert spaces to tabs | |
| Use POSIX standard |
"+this.formattedReference()+"
"),this},formattedReference:function(){var t=this.model,e=t.entryType(),i=t.fields(),n="",s=this._asSentence((i.author?i.author:"")+(i.year?" ("+i.year+")":""))+" ",a=i.title||"",o=i.pages?"pp. "+i.pages:"",r=i.address;if("article"==e){var l=(i.volume?i.volume:"")+(i.number?" ("+i.number+")":"")+(o?", "+o:"");n=s+this._asSentence(a)+(i.journal?"In "+i.journal+", ":"")+this._asSentence(l)+this._asSentence(i.address)+""}else n="inproceedings"==e||"proceedings"==e?s+this._asSentence(a)+(i.booktitle?"In "+i.booktitle+", ":"")+(o?o:"")+(r?", "+r:"")+".":"mastersthesis"==e||"phdthesis"==e?s+this._asSentence(a)+(i.howpublished?i.howpublished+". ":"")+(i.note?i.note+".":""):"techreport"==e?s+this._asSentence(a)+this._asSentence(i.institution)+this._asSentence(i.number)+this._asSentence(i.type):"book"==e||"inbook"==e||"incollection"==e?s+" "+this._formatBookInfo(i):s+" "+this._asSentence(a)+this._asSentence(i.howpublished)+this._asSentence(i.note);var c="";i.doi&&(c="http://dx.doi.org/"+i.doi,n+='[doi:'+i.doi+"]");var h=i.url||c;return h&&(n+='[Link]'),n},_formatBookInfo:function(t){var e="";return t.chapter&&(e+=t.chapter+" in "),t.title&&(e+=""+t.title+""),t.editor&&(e+=", Edited by "+t.editor+", "),t.publisher&&(e+=", "+t.publisher),t.pages&&(e+=", pp. "+t.pages),t.series&&(e+=", "+t.series+""),t.volume&&(e+=", Vol."+t.volume),t.issn&&(e+=", ISBN: "+t.issn),e+"."},_asSentence:function(t){return t&&t.trim()?t+". ":""}}),s=a.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(t){var e=new n({model:t});this.$(".citations-formatted").append(e.render().el);var i=this.$(".citations-bibtex-text");i.val(i.val()+"\n\r"+t.attributes.content)},render:function(){this.$el.html(this.citationsElement()),this.collection.each(function(t){this.renderCitation(t)},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(t.responseJSON)+"":": "+i),creator._showAlert(n,"alert-danger")},render:function(t,e){return this.$el.empty().html(p.templates.main()),this._renderHeader(t),this._renderMiddle(t),this._renderFooter(t),this._addPluginComponents(),this.trigger("rendered",this),this},_renderHeader:function(t,e){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(t,e){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(t,e){var i,n,a=this,o=[],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(t,e){return void 0!==r[0][e]&&r[0][e]!==t&&o.push(a._renderPairButton()),a._renderUnpairedDataset(t)}),i=r[0].map(function(t){return a._renderUnpairedDataset(t)}),i.length||n.length?(this.$(".unpaired-columns .forward-column .column-datasets").append(i).add(this.$(".unpaired-columns .paired-column .column-datasets").append(o)).add(this.$(".unpaired-columns .reverse-column .column-datasets").append(n)),void this._adjUnpairedOnScrollbar()):void this._renderUnpairedNotShown())},_renderUnpairedDisplayStr:function(t){return["(",t," ",s("filtered out"),")"].join("")},_renderUnpairedDataset:function(t){return l("").attr("id","dataset-"+t.id).addClass("dataset unpaired").attr("draggable",!0).addClass(t.selected?"selected":"").append(l("").addClass("dataset-name").text(t.name)).data("dataset",t)},_renderPairButton:function(){return l("").addClass("dataset unpaired").append(l("").addClass("dataset-name").text(s("Pair these datasets")))},_renderUnpairedEmpty:function(){var t=l('').text("("+s("no remaining unpaired datasets")+")");return this.$(".unpaired-columns .paired-column .column-datasets").empty().prepend(t),t},_renderUnpairedNotShown:function(){var t=l('').text("("+s("no datasets were found matching the current filters")+")");return this.$(".unpaired-columns .paired-column .column-datasets").empty().prepend(t),t},_adjUnpairedOnScrollbar:function(){var t=this.$(".unpaired-columns").last(),e=this.$(".unpaired-columns .reverse-column .dataset").first();if(e.size()){var i=t.offset().left+t.outerWidth(),n=e.offset().left+e.outerWidth(),s=Math.floor(i)-Math.floor(n);this.$(".unpaired-columns .forward-column").css("margin-left",s>0?s:0)}},_renderPaired:function(t,e){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(t,e){var n=new u({pair:t});i.$(".paired-columns .column-datasets").append(n.render().$el).append(['"].join(""))})},_renderPairedEmpty:function(){var t=l('').text("("+s("no paired datasets yet")+")");return this.$(".paired-columns .column-datasets").empty().prepend(t),t},_renderFooter:function(t,e){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(t){function e(t,e){return['"].join("")}var i=l(o.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 f=function(t,e){var i,n=r.Deferred();if(e=o.defaults(e||{},{datasets:t,oncancel:function(){Galaxy.modal.hide(),n.reject("cancelled")},oncreate:function(t,e){Galaxy.modal.hide(),n.resolve(e)}}),!window.Galaxy||!Galaxy.modal)throw new Error("Galaxy or Galaxy.modal not found");return i=new p(e),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:f,createListOfPairsCollection:h}}.apply(e,n),!(void 0!==s&&(t.exports=s))}).call(e,i(2),i(3),i(1),i(1))},function(t,e,i){var n,s;(function(a,o,r){n=[i(21),i(23),i(6),i(5)],s=function(t,e,i,n){function s(t){var e=t.toJSON(),i=u(e,{creationFn:function(e,i){return e=[{name:"forward",src:"hda",id:e[0].id},{name:"reverse",src:"hda",id:e[1].id}],t.createHDCA(e,"paired",i)}});return i}var l="collections",c=a.View.extend(i.LoggableMixin).extend({_logNamespace:l,tagName:"li",className:"collection-element",initialize:function(t){this.element=t.element||{},this.identifier=t.identifier},render:function(){return this.$el.attr("data-element-id",this.element.id).html(this.template({identifier:this.identifier,element:this.element})),this},template:o.template(['<%= identifier %>','<%= element.name %>'].join("")),destroy:function(){this.off(),this.$el.remove()},toString:function(){return"DatasetCollectionElementView()"}}),h=t.ListCollectionCreator,d=h.extend({elementViewClass:c,collectionClass:e.HistoryPairDatasetCollection,className:"pair-collection-creator collection-creator flex-row-container",_mangleDuplicateNames:function(){},render:function(t,e){return 2===this.workingElements.length?h.prototype.render.call(this,t,e):this._renderInvalid(t,e)},_renderList:function(t,e){var i=this,n=r(""),s=i.$list();o.each(this.elementViews,function(t){t.destroy(),i.removeElementView(t)}),n.append(i._createForwardElementView().$el),n.append(i._createReverseElementView().$el),s.empty().append(n.children()),o.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(t,e){var i=new this.elementViewClass(o.extend(e,{element:t}));return this.elementViews.push(i),i},swap:function(){this.workingElements=[this.workingElements[1],this.workingElements[0]],this._renderList()},events:o.extend(o.clone(h.prototype.events),{"click .swap":"swap"}),templates:o.extend(o.clone(h.prototype.templates),{middle:o.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:o.template(['| Description | Name | Size | Settings | Status |
|---|
| Name | Size | Type | Genome | Settings | Status |
|---|
' + njobs_text + ' been successfully added to the queue - resulting in the following datasets:
'; + for (var i in response.outputs) { + tmpl += '' + response.outputs[i].hid + ': ' + response.outputs[i].name + '
'; + } + tmpl += 'You can check the status of queued jobs and view the resulting data by refreshing the History pane. When the job has been run the status will change from \'running\' to \'finished\' if completed successfully or \'error\' if problems were encountered.
' + + '' + + 'The server could not complete the request. Please contact the Galaxy Team if this error persists.' + + '
' + + '' + + '" + this.formattedReference() + "
" ); + return this; + }, + formattedReference: function() { + var model = this.model; + var entryType = model.entryType(); + var fields = model.fields(); + + var ref = ""; + // Code inspired by... + // https://github.com/vkaravir/bib-publication-list/blob/master/src/bib-publication-list.js + var authorsAndYear = this._asSentence( (fields.author ? fields.author : "") + (fields.year ? (" (" + fields.year + ")") : "") ) + " "; + var title = fields.title || ""; + var pages = fields.pages ? ("pp. " + fields.pages) : ""; + var address = fields.address; + if( entryType == "article" ) { + var volume = (fields.volume ? fields.volume : "") + + (fields.number ? ( " (" + fields.number + ")" ) : "") + + (pages ? ", " + pages : ""); + ref = authorsAndYear + this._asSentence(title) + + (fields.journal ? ("In " + fields.journal + ", ") : "") + + this._asSentence(volume) + + this._asSentence(fields.address) + + "<\/em>"; + } else if( entryType == "inproceedings" || entryType == "proceedings" ) { + ref = authorsAndYear + + this._asSentence(title) + + (fields.booktitle ? ("In " + fields.booktitle + ", ") : "") + + (pages ? pages : "") + + (address ? ", " + address : "") + + ".<\/em>"; + } else if( entryType == "mastersthesis" || entryType == "phdthesis" ) { + ref = authorsAndYear + this._asSentence(title) + + (fields.howpublished ? fields.howpublished + ". " : "") + + (fields.note ? fields.note + "." : ""); + } else if( entryType == "techreport" ) { + ref = authorsAndYear + this._asSentence(title) + + this._asSentence(fields.institution) + + this._asSentence(fields.number) + + this._asSentence(fields.type); + } else if( entryType == "book" || entryType == "inbook" || entryType == "incollection" ) { + ref = authorsAndYear + " " + this._formatBookInfo(fields); + } else { + ref = authorsAndYear + " " + this._asSentence(title) + + this._asSentence(fields.howpublished) + + this._asSentence(fields.note); + } + var doiUrl = ""; + if( fields.doi ) { + doiUrl = 'http://dx.doi.org/' + fields.doi; + ref += '[doi:' + fields.doi + "]"; + } + var url = fields.url || doiUrl; + if( url ) { + ref += '[Link]'; + } + return ref; + }, + _formatBookInfo: function(fields) { + var info = ""; + if( fields.chapter ) { + info += fields.chapter + " in "; + } + if( fields.title ) { + info += "" + fields.title + "<\/em>"; + } + if( fields.editor ) { + info += ", Edited by " + fields.editor + ", "; + } + if( fields.publisher) { + info += ", " + fields.publisher; + } + if( fields.pages ) { + info += ", pp. " + fields.pages + ""; + } + if( fields.series ) { + info += ", " + fields.series + "<\/em>"; + } + if( fields.volume ) { + info += ", Vol." + fields.volume; + } + if( fields.issn ) { + info += ", ISBN: " + fields.issn; + } + return info + "."; + }, + _asSentence: function(str) { + return (str && str.trim()) ? str + ". " : ""; + } + }); + + var CitationListView = Backbone.View.extend({ + el: '#citations', + /** + * Set up view. + */ + initialize: function() { + this.listenTo( this.collection, 'add', this.renderCitation ); + }, + + events: { + 'click .citations-to-bibtex': 'showBibtex', + 'click .citations-to-formatted': 'showFormatted' + }, + + renderCitation: function( citation ) { + var citationView = new CitationView( { model: citation } ); + this.$(".citations-formatted").append( citationView.render().el ); + var rawTextarea = this.$(".citations-bibtex-text"); + rawTextarea.val( rawTextarea.val() + "\n\r" + citation.attributes.content ); + }, + + render: function() { + this.$el.html(this.citationsElement()); + this.collection.each(function( item ){ + this.renderCitation( item ); + }, 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() { + if( this.collection.partial ) { + return [ + '| Name | ' + + 'Size | ' + + 'Type | ' + + 'Genome | ' + + 'Settings | ' + + 'Status | ' + + '' + + ' |
|---|
| ' + + ' | Convert spaces to tabs | ' + + '
| ' + + ' | Use POSIX standard | ' + + '
| ' + + ' | Name | ' + + 'Size | ' + + 'Created | ' + + '
|---|
| ' + + ' | ' + + ' | Description | ' + + 'Name | ' + + 'Size | ' + + 'Settings | ' + + 'Status | ' + + '
|---|
' + JSON.stringify( xhr.responseJSON ) + ''; + } else { + content += ': ' + message; + } + } + creator._showAlert( content, 'alert-danger' ); + }, + + events : { + // header + 'click .more-help' : '_clickMoreHelp', + 'click .less-help' : '_clickLessHelp', + 'click .main-help' : '_toggleHelp', + 'click .header .alert button' : '_hideAlert', + + 'click .reset' : 'reset', + 'click .clear-selected' : 'clearSelectedElements', + + // elements - selection + 'click .collection-elements' : 'clearSelectedElements', + + // elements - drop target + // 'dragenter .collection-elements': '_dragenterElements', + // 'dragleave .collection-elements': '_dragleaveElements', + 'dragover .collection-elements' : '_dragoverElements', + 'drop .collection-elements' : '_dropElements', + + // these bubble up from the elements as custom events + 'collection-element.dragstart .collection-elements' : '_elementDragstart', + 'collection-element.dragend .collection-elements' : '_elementDragend', + + // footer + 'change .collection-name' : '_changeName', + 'keydown .collection-name' : '_nameCheckForEnter', + 'click .cancel-create' : function( ev ){ + if( typeof this.oncancel === 'function' ){ + this.oncancel.call( this ); + } + }, + 'click .create-collection' : '_clickCreate'//, + }, + + // ........................................................................ header + /** expand help */ + _clickMoreHelp : function( ev ){ + ev.stopPropagation(); + this.$( '.main-help' ).addClass( 'expanded' ); + this.$( '.more-help' ).hide(); + }, + /** collapse help */ + _clickLessHelp : function( ev ){ + ev.stopPropagation(); + this.$( '.main-help' ).removeClass( 'expanded' ); + this.$( '.more-help' ).show(); + }, + /** toggle help */ + _toggleHelp : function( ev ){ + ev.stopPropagation(); + this.$( '.main-help' ).toggleClass( 'expanded' ); + this.$( '.more-help' ).toggle(); + }, + + /** show an alert on the top of the interface containing message (alertClass is bootstrap's alert-*) */ + _showAlert : function( message, alertClass ){ + alertClass = alertClass || 'alert-danger'; + this.$( '.main-help' ).hide(); + this.$( '.header .alert' ) + .attr( 'class', 'alert alert-dismissable' ).addClass( alertClass ).show() + .find( '.alert-message' ).html( message ); + }, + /** hide the alerts at the top */ + _hideAlert : function( message ){ + this.$( '.main-help' ).show(); + this.$( '.header .alert' ).hide(); + }, + + // ........................................................................ elements + /** reset all data to the initial state */ + reset : function(){ + this._instanceSetUp(); + this._elementsSetUp(); + this.render(); + }, + + /** deselect all elements */ + clearSelectedElements : function( ev ){ + this.$( '.collection-elements .collection-element' ).removeClass( 'selected' ); + this.$( '.collection-elements-controls > .clear-selected' ).hide(); + }, + + //_dragenterElements : function( ev ){ + // //this.debug( '_dragenterElements:', ev ); + //}, + //TODO: if selected are dragged out of the list area - remove the placeholder - cuz it won't work anyway + // _dragleaveElements : function( ev ){ + // //this.debug( '_dragleaveElements:', ev ); + // }, + + /** track the mouse drag over the list adding a placeholder to show where the drop would occur */ + _dragoverElements : function( ev ){ + //this.debug( '_dragoverElements:', ev ); + ev.preventDefault(); + + var $list = this.$list(); + this._checkForAutoscroll( $list, ev.originalEvent.clientY ); + var $nearest = this._getNearestElement( ev.originalEvent.clientY ); + + //TODO: no need to re-create - move instead + this.$( '.element-drop-placeholder' ).remove(); + var $placeholder = $( '' ); + if( !$nearest.size() ){ + $list.append( $placeholder ); + } else { + $nearest.before( $placeholder ); + } + }, + + /** If the mouse is near enough to the list's top or bottom, scroll the list */ + _checkForAutoscroll : function( $element, y ){ + var AUTOSCROLL_SPEED = 2, + offset = $element.offset(), + scrollTop = $element.scrollTop(), + upperDist = y - offset.top, + lowerDist = ( offset.top + $element.outerHeight() ) - y; + if( upperDist >= 0 && upperDist < this.autoscrollDist ){ + $element.scrollTop( scrollTop - AUTOSCROLL_SPEED ); + } else if( lowerDist >= 0 && lowerDist < this.autoscrollDist ){ + $element.scrollTop( scrollTop + AUTOSCROLL_SPEED ); + } + }, + + /** get the nearest element based on the mouse's Y coordinate. + * If the y is at the end of the list, return an empty jQuery object. + */ + _getNearestElement : function( y ){ + var WIGGLE = 4, + lis = this.$( '.collection-elements li.collection-element' ).toArray(); + for( var i=0; i
', _l([ + '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( '' )), '
', + '', _l([ + 'Once your collection is complete, enter a name and ', + 'click "Create list".' + ].join( '' )), '
' + ].join('')), + + /** shown in list when all elements are discarded */ + invalidElements : _.template([ + _l( 'The following selections could not be included due to problems:' ), + '<%= dataset.peek %>', + '<% } %>', + '<% } %>', + '
', _l([ + '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( '' )), '
', + '', _l([ + 'Once your collection is complete, enter a name and ', + 'click "Create list".' + ].join( '' )), '
' + ].join('')), + + /** a simplified page communicating what went wrong and why the user needs to reselect something else */ + invalidInitial : _.template([ + '' + JSON.stringify( xhr.responseJSON ) + ''; + } else { + content += ': ' + message; + } + } + creator._showAlert( content, 'alert-danger' ); + }, + + // ------------------------------------------------------------------------ rendering + /** render the entire interface */ + render : function( speed, callback ){ + //this.debug( '-- _render' ); + //this.$el.empty().html( PairedCollectionCreator.templates.main() ); + this.$el.empty().html( PairedCollectionCreator.templates.main() ); + this._renderHeader( speed ); + this._renderMiddle( speed ); + this._renderFooter( speed ); + this._addPluginComponents(); + this.trigger( 'rendered', this ); + return this; + }, + + /** render the header section */ + _renderHeader : function( speed, callback ){ + //this.debug( '-- _renderHeader' ); + var $header = this.$( '.header' ).empty().html( PairedCollectionCreator.templates.header() ) + .find( '.help-content' ).prepend( $( PairedCollectionCreator.templates.helpContent() ) ); + + this._renderFilters(); + return $header; + }, + /** fill the filter inputs with the filter values */ + _renderFilters : function(){ + return this.$( '.forward-column .column-header input' ).val( this.filters[0] ) + .add( this.$( '.reverse-column .column-header input' ).val( this.filters[1] ) ); + }, + + /** render the middle including unpaired and paired sections (which may be hidden) */ + _renderMiddle : function( speed, callback ){ + var $middle = this.$( '.middle' ).empty().html( PairedCollectionCreator.templates.middle() ); + + // (re-) hide the un/paired panels based on instance vars + if( this.unpairedPanelHidden ){ + this.$( '.unpaired-columns' ).hide(); + } else if( this.pairedPanelHidden ){ + this.$( '.paired-columns' ).hide(); + } + + this._renderUnpaired(); + this._renderPaired(); + return $middle; + }, + /** render the unpaired section, showing datasets accrd. to filters, update the unpaired counts */ + _renderUnpaired : function( speed, callback ){ + //this.debug( '-- _renderUnpaired' ); + var creator = this, + $fwd, $rev, $prd = [], + split = this._splitByFilters(); + // update unpaired counts + this.$( '.forward-column .title' ) + .text([ split[0].length, _l( 'unpaired forward' ) ].join( ' ' )); + this.$( '.forward-column .unpaired-info' ) + .text( this._renderUnpairedDisplayStr( this.unpaired.length - split[0].length ) ); + this.$( '.reverse-column .title' ) + .text([ split[1].length, _l( 'unpaired reverse' ) ].join( ' ' )); + this.$( '.reverse-column .unpaired-info' ) + .text( this._renderUnpairedDisplayStr( this.unpaired.length - split[1].length ) ); + + this.$( '.unpaired-columns .column-datasets' ).empty(); + + // show/hide the auto pair button if any unpaired are left + this.$( '.autopair-link' ).toggle( this.unpaired.length !== 0 ); + if( this.unpaired.length === 0 ){ + this._renderUnpairedEmpty(); + return; + } + + // create the dataset dom arrays + $rev = split[1].map( function( dataset, i ){ + // if there'll be a fwd dataset across the way, add a button to pair the row + if( ( split[0][ i ] !== undefined ) + && ( split[0][ i ] !== dataset ) ){ + $prd.push( creator._renderPairButton() ); + } + return creator._renderUnpairedDataset( dataset ); + }); + $fwd = split[0].map( function( dataset ){ + return creator._renderUnpairedDataset( dataset ); + }); + + if( !$fwd.length && !$rev.length ){ + this._renderUnpairedNotShown(); + return; + } + // add to appropo cols + //TODO: not the best way to render - consider rendering the entire unpaired-columns section in a fragment + // and swapping out that + this.$( '.unpaired-columns .forward-column .column-datasets' ).append( $fwd ) + .add( this.$( '.unpaired-columns .paired-column .column-datasets' ).append( $prd ) ) + .add( this.$( '.unpaired-columns .reverse-column .column-datasets' ).append( $rev ) ); + this._adjUnpairedOnScrollbar(); + }, + /** return a string to display the count of filtered out datasets */ + _renderUnpairedDisplayStr : function( numFiltered ){ + return [ '(', numFiltered, ' ', _l( 'filtered out' ), ')' ].join(''); + }, + /** return an unattached jQuery DOM element to represent an unpaired dataset */ + _renderUnpairedDataset : function( dataset ){ + //TODO: to underscore template + return $( '') + .attr( 'id', 'dataset-' + dataset.id ) + .addClass( 'dataset unpaired' ) + .attr( 'draggable', true ) + .addClass( dataset.selected? 'selected': '' ) + .append( $( '' ).addClass( 'dataset-name' ).text( dataset.name ) ) + //?? + .data( 'dataset', dataset ); + }, + /** render the button that may go between unpaired datasets, allowing the user to pair a row */ + _renderPairButton : function(){ + //TODO: *not* a dataset - don't pretend like it is + return $( '').addClass( 'dataset unpaired' ) + .append( $( '' ).addClass( 'dataset-name' ).text( _l( 'Pair these datasets' ) ) ); + }, + /** a message to display when no unpaired left */ + _renderUnpairedEmpty : function(){ + //this.debug( '-- renderUnpairedEmpty' ); + var $msg = $( '' ) + .text( '(' + _l( 'no remaining unpaired datasets' ) + ')' ); + this.$( '.unpaired-columns .paired-column .column-datasets' ).empty().prepend( $msg ); + return $msg; + }, + /** a message to display when no unpaired can be shown with the current filters */ + _renderUnpairedNotShown : function(){ + //this.debug( '-- renderUnpairedEmpty' ); + var $msg = $( '' ) + .text( '(' + _l( 'no datasets were found matching the current filters' ) + ')' ); + this.$( '.unpaired-columns .paired-column .column-datasets' ).empty().prepend( $msg ); + return $msg; + }, + /** try to detect if the unpaired section has a scrollbar and adjust left column for better centering of all */ + _adjUnpairedOnScrollbar : function(){ + var $unpairedColumns = this.$( '.unpaired-columns' ).last(), + $firstDataset = this.$( '.unpaired-columns .reverse-column .dataset' ).first(); + if( !$firstDataset.size() ){ return; } + var ucRight = $unpairedColumns.offset().left + $unpairedColumns.outerWidth(), + dsRight = $firstDataset.offset().left + $firstDataset.outerWidth(), + rightDiff = Math.floor( ucRight ) - Math.floor( dsRight ); + //this.debug( 'rightDiff:', ucRight, '-', dsRight, '=', rightDiff ); + this.$( '.unpaired-columns .forward-column' ) + .css( 'margin-left', ( rightDiff > 0 )? rightDiff: 0 ); + }, + + /** render the paired section and update counts of paired datasets */ + _renderPaired : function( speed, callback ){ + //this.debug( '-- _renderPaired' ); + this.$( '.paired-column-title .title' ).text([ this.paired.length, _l( 'paired' ) ].join( ' ' ) ); + // show/hide the unpair all link + this.$( '.unpair-all-link' ).toggle( this.paired.length !== 0 ); + if( this.paired.length === 0 ){ + this._renderPairedEmpty(); + return; + //TODO: would be best to return here (the $columns) + } else { + // show/hide 'remove extensions link' when any paired and they seem to have extensions + this.$( '.remove-extensions-link' ).show(); + } + + this.$( '.paired-columns .column-datasets' ).empty(); + var creator = this; + this.paired.forEach( function( pair, i ){ + //TODO: cache these? + var pairView = new PairView({ pair: pair }); + creator.$( '.paired-columns .column-datasets' ) + .append( pairView.render().$el ) + .append([ + '' + ].join( '' )); + }); + }, + /** a message to display when none paired */ + _renderPairedEmpty : function(){ + var $msg = $( '' ) + .text( '(' + _l( 'no paired datasets yet' ) + ')' ); + this.$( '.paired-columns .column-datasets' ).empty().prepend( $msg ); + return $msg; + }, + + /** render the footer, completion controls, and cancel controls */ + _renderFooter : function( speed, callback ){ + var $footer = this.$( '.footer' ).empty().html( PairedCollectionCreator.templates.footer() ); + this.$( '.remove-extensions' ).prop( 'checked', this.removeExtensions ); + if( typeof this.oncancel === 'function' ){ + this.$( '.cancel-create.btn' ).show(); + } + return $footer; + }, + + /** add any jQuery/bootstrap/custom plugins to elements rendered */ + _addPluginComponents : function(){ + this._chooseFiltersPopover( '.choose-filters-link' ); + this.$( '.help-content i' ).hoverhighlight( '.collection-creator', 'rgba( 64, 255, 255, 1.0 )' ); + }, + + /** build a filter selection popover allowing selection of common filter pairs */ + _chooseFiltersPopover : function( selector ){ + function filterChoice( val1, val2 ){ + return [ + '' + ].join(''); + } + var $popoverContent = $( _.template([ + '
', _l([ + '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( '' )), '
', + '', _l([ + 'Unpaired datasets are shown in the unpaired section ', + '(hover over the underlined words to highlight below). ', + 'Paired datasets are shown in the paired section.', + '
', _l([ + '
', _l([ + 'To unpair individual dataset pairs, click the ', + 'unpair buttons ( ). ', + 'Click the "Unpair all" link to unpair all pairs.' + ].join( '' )), '
', + '', _l([ + '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( '' )), '
', + '', _l([ + '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('')) + }; + + + //============================================================================= + /** a modal version of the paired collection creator */ + var pairedCollectionCreatorModal = function _pairedCollectionCreatorModal( datasets, options ){ + + var deferred = jQuery.Deferred(), + creator; + + options = _.defaults( options || {}, { + datasets : datasets, + oncancel : function(){ + Galaxy.modal.hide(); + deferred.reject( 'cancelled' ); + }, + oncreate : function( creator, response ){ + Galaxy.modal.hide(); + deferred.resolve( response ); + } + }); + + if( !window.Galaxy || !Galaxy.modal ){ + throw new Error( 'Galaxy or Galaxy.modal not found' ); + } + + creator = new PairedCollectionCreator( options ); + Galaxy.modal.show({ + title : 'Create a collection of paired datasets', + body : creator.$el, + width : '80%', + height : '800px', + closing_events: true + }); + creator.render(); + window.creator = creator; + + //TODO: remove modal header + return deferred; + }; + + + //============================================================================= + function createListOfPairsCollection( collection ){ + var elements = collection.toJSON(); + //TODO: validate elements + return pairedCollectionCreatorModal( elements, { + historyId : collection.historyId + }); + } + + + //============================================================================= + return { + PairedCollectionCreator : PairedCollectionCreator, + pairedCollectionCreatorModal : pairedCollectionCreatorModal, + createListOfPairsCollection : createListOfPairsCollection + }; + }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(/*! libs/backbone */ 4), __webpack_require__(/*! underscore */ 1), __webpack_require__(/*! jquery */ 2), __webpack_require__(/*! jquery */ 2))) + +/***/ }, +/* 98 */ +/*!*********************************************!*\ + !*** ./galaxy/scripts/utils/levenshtein.js ***! + \*********************************************/ +/***/ function(module, exports, __webpack_require__) { + + var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ + ], __WEBPACK_AMD_DEFINE_RESULT__ = function(){ + //============================================================================= + /** + (Imported for edit distance algorith. From: https://gist.github.com/andrei-m/982927) + Copyright (c) 2011 Andrei Mackenzie + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit + persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + // Compute the edit distance between the two given strings + //exports.getEditDistance = function(a, b){ + function levenshteinDistance(a, b){ + if(a.length === 0){ return b.length; } + if(b.length === 0){ return a.length; } + + var matrix = []; + + // increment along the first column of each row + var i; + for(i = 0; i <= b.length; i++){ + matrix[i] = [i]; + } + + // increment each column in the first row + var j; + for(j = 0; j <= a.length; j++){ + matrix[0][j] = j; + } + + // Fill in the rest of the matrix + for(i = 1; i <= b.length; i++){ + for(j = 1; j <= a.length; j++){ + if(b.charAt(i-1) === a.charAt(j-1)){ + matrix[i][j] = matrix[i-1][j-1]; + } else { + matrix[i][j] = Math.min(matrix[i-1][j-1] + 1, // substitution + Math.min(matrix[i][j-1] + 1, // insertion + matrix[i-1][j] + 1)); // deletion + } + } + } + + //console.debug( '\t\t levenshteinDistance', a, b, matrix[b.length][a.length] ); + return matrix[b.length][a.length]; + } + + //============================================================================= + return levenshteinDistance; + }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + + +/***/ }, +/* 99 */ +/*!***************************************!*\ + !*** ./galaxy/scripts/layout/page.js ***! + \***************************************/ +/***/ function(module, exports, __webpack_require__) { + + var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, $) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ + __webpack_require__(/*! layout/masthead */ 100), + __webpack_require__(/*! layout/panel */ 3), + __webpack_require__(/*! mvc/ui/ui-modal */ 13), + __webpack_require__(/*! mvc/base-mvc */ 5) + ], __WEBPACK_AMD_DEFINE_RESULT__ = function( MASTHEAD, PANEL, MODAL, BASE_MVC ) { + + // ============================================================================ + var PageLayoutView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({ + _logNamespace : 'layout', + + el : 'body', + className : 'full-content', + + _panelIds : [ + 'left', 'center', 'right' + ], + + defaultOptions : { + message_box_visible : false, + message_box_content : '', + message_box_class : 'info', + + show_inactivity_warning : false, + inactivity_box_content : '', + }, + + initialize : function( options ){ + this.log( this + '.initialize:', options ); + _.extend( this, _.pick( options, this._panelIds ) ); + this.options = _.defaults( _.omit( options, this._panelIds ), this.defaultOptions ); + }, + + /** */ + $everything : function(){ + return this.$( '#everything' ); + }, + + render : function(){ + this.log( this + '.render:' ); + this.$el.attr( 'scroll', 'no' ); + this.$el.html( this.template( this.options ) ); + + this.modal = new MODAL.View(); + //TODO: no render on masthead, needs init each time + this.masthead = new MASTHEAD.GalaxyMasthead( this.options.config ); + + // TODO: remove globals + Galaxy.modal = this.modal; + Galaxy.masthead = this.masthead; + + if( this.options.message_box_visible ){ + this.messageBox( this.options.message_box_content, this.options.message_box_class ); + } + if( this.options.show_inactivity_warning ){ + this.inactivityWarning( this.options.inactivity_box_content ); + } + + this.renderPanels(); + return this; + }, + + /** */ + messageBox : function( content, level ){ + content = content || ''; + level = level || 'info'; + this.$el.addClass( 'has-message-box' ); + this.$( '#messagebox' ) + .attr( 'class', 'panel-' + level + '-message' ) + .html( content ) + .toggle( !!content ); + return this; + }, + + /** */ + inactivityWarning : function( content ){ + var verificationLink = 'Resend verification.'; + this.$el.addClass( 'has-inactivity-box' ); + this.$( '#inactivebox' ) + .html( content ) + .append( ' ' + verificationLink ) + .toggle( !!content ); + return this; + }, + + /** */ + renderPanels : function(){ + var page = this; + // TODO: Remove this line after select2 update + $( '.select2-hidden-accessible' ).remove(); + this._panelIds.forEach( function( panelId ){ + if( _.has( page, panelId ) ){ + var panelView = page[ panelId ]; + panelView.setElement( '#' + panelId ); + panelView.render(); + } + }); + return this; + }, + + /** body template */ + //TODO: to underscore + template: function( options ){ + //TODO: remove inline styling + return [ + '' + 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.size() ){\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 '
' + njobs_text + ' been successfully added to the queue - resulting in the following datasets:
';\n\t for (var i in response.outputs) {\n\t tmpl += '' + response.outputs[i].hid + ': ' + response.outputs[i].name + '
';\n\t }\n\t tmpl += 'You can check the status of queued jobs and view the resulting data by refreshing the History pane. When the job has been run the status will change from \\'running\\' to \\'finished\\' if completed successfully or \\'error\\' if problems were encountered.
' +\n\t '' +\n\t 'The server could not complete the request. Please contact the Galaxy Team if this error persists.' +\n\t '
' +\n\t '' +\n\t '| ' +\n\t ' | Name | ' +\n\t 'Size | ' +\n\t 'Created | ' +\n\t '
|---|
| ' +\n\t ' | Convert spaces to tabs | ' +\n\t '
| ' +\n\t ' | Use POSIX standard | ' +\n\t '
\" + 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 // ------------------------------------------------------------------------ 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.size() ){ 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__(2), __webpack_require__(3), __webpack_require__(1), __webpack_require__(1)))\n\n/***/ },\n/* 85 */\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__(21),\n\t __webpack_require__(23),\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\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 '| ' +\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 ' |
|---|
' + 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.size() ){\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 '
' + njobs_text + ' been successfully added to the queue - resulting in the following datasets:
';\n for (var i in response.outputs) {\n tmpl += '' + response.outputs[i].hid + ': ' + response.outputs[i].name + '
';\n }\n tmpl += 'You can check the status of queued jobs and view the resulting data by refreshing the History pane. When the job has been run the status will change from \\'running\\' to \\'finished\\' if completed successfully or \\'error\\' if problems were encountered.
' +\n '' +\n 'The server could not complete the request. Please contact the Galaxy Team if this error persists.' +\n '
' +\n '' +\n '| ' +\n ' | Name | ' +\n 'Size | ' +\n 'Created | ' +\n '
|---|
| ' +\n ' | Convert spaces to tabs | ' +\n '
| ' +\n ' | Use POSIX standard | ' +\n '
\" + 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 // ------------------------------------------------------------------------ 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.size() ){ 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 = 84\n ** module chunks = 2\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\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 '| ' +\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 ' |
|---|
' + njobs_text + ' been successfully added to the queue - resulting in the following datasets:
';\n for (var i in response.outputs) {\n tmpl += '' + response.outputs[i].hid + ': ' + response.outputs[i].name + '
';\n }\n tmpl += 'You can check the status of queued jobs and view the resulting data by refreshing the History pane. When the job has been run the status will change from \\'running\\' to \\'finished\\' if completed successfully or \\'error\\' if problems were encountered.
' +\n '' +\n 'The server could not complete the request. Please contact the Galaxy Team if this error persists.' +\n '
' +\n '' +\n '\" + 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 '| Name | ' +\n 'Size | ' +\n 'Type | ' +\n 'Genome | ' +\n 'Settings | ' +\n 'Status | ' +\n '' +\n ' |
|---|
| ' +\n ' | Convert spaces to tabs | ' +\n '
| ' +\n ' | Use POSIX standard | ' +\n '
| ' +\n ' | Name | ' +\n 'Size | ' +\n 'Created | ' +\n '
|---|
| ' +\n ' | ' +\n ' | Description | ' +\n 'Name | ' +\n 'Size | ' +\n 'Settings | ' +\n 'Status | ' +\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.size() ){\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 '
', _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 '' + 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.size() ){ 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 = 97\n ** module chunks = 0\n **/","define([\n], function(){\n//=============================================================================\n/**\n(Imported for edit distance algorith. From: https://gist.github.com/andrei-m/982927)\nCopyright (c) 2011 Andrei Mackenzie\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the\nrights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit\npersons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the\nSoftware.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\nWARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\nOTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n*/\n// Compute the edit distance between the two given strings\n//exports.getEditDistance = function(a, b){\nfunction levenshteinDistance(a, b){\n if(a.length === 0){ return b.length; }\n if(b.length === 0){ return a.length; }\n\n var matrix = [];\n\n // increment along the first column of each row\n var i;\n for(i = 0; i <= b.length; i++){\n matrix[i] = [i];\n }\n\n // increment each column in the first row\n var j;\n for(j = 0; j <= a.length; j++){\n matrix[0][j] = j;\n }\n\n // Fill in the rest of the matrix\n for(i = 1; i <= b.length; i++){\n for(j = 1; j <= a.length; j++){\n if(b.charAt(i-1) === a.charAt(j-1)){\n matrix[i][j] = matrix[i-1][j-1];\n } else {\n matrix[i][j] = Math.min(matrix[i-1][j-1] + 1, // substitution\n Math.min(matrix[i][j-1] + 1, // insertion\n matrix[i-1][j] + 1)); // deletion\n }\n }\n }\n\n //console.debug( '\\t\\t levenshteinDistance', a, b, matrix[b.length][a.length] );\n return matrix[b.length][a.length];\n}\n\n//=============================================================================\n return levenshteinDistance;\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/utils/levenshtein.js\n ** module id = 98\n ** module chunks = 0\n **/","define([\n 'layout/masthead',\n 'layout/panel',\n 'mvc/ui/ui-modal',\n 'mvc/base-mvc'\n], function( MASTHEAD, PANEL, MODAL, BASE_MVC ) {\n\n// ============================================================================\nvar PageLayoutView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n _logNamespace : 'layout',\n\n el : 'body',\n className : 'full-content',\n\n _panelIds : [\n 'left', 'center', 'right'\n ],\n\n defaultOptions : {\n message_box_visible : false,\n message_box_content : '',\n message_box_class : 'info',\n\n show_inactivity_warning : false,\n inactivity_box_content : '',\n },\n\n initialize : function( options ){\n this.log( this + '.initialize:', options );\n _.extend( this, _.pick( options, this._panelIds ) );\n this.options = _.defaults( _.omit( options, this._panelIds ), this.defaultOptions );\n },\n\n /** */\n $everything : function(){\n return this.$( '#everything' );\n },\n\n render : function(){\n this.log( this + '.render:' );\n this.$el.attr( 'scroll', 'no' );\n this.$el.html( this.template( this.options ) );\n\n this.modal = new MODAL.View();\n //TODO: no render on masthead, needs init each time\n this.masthead = new MASTHEAD.GalaxyMasthead( this.options.config );\n\n // TODO: remove globals\n Galaxy.modal = this.modal;\n Galaxy.masthead = this.masthead;\n\n if( this.options.message_box_visible ){\n this.messageBox( this.options.message_box_content, this.options.message_box_class );\n }\n if( this.options.show_inactivity_warning ){\n this.inactivityWarning( this.options.inactivity_box_content );\n }\n\n this.renderPanels();\n return this;\n },\n\n /** */\n messageBox : function( content, level ){\n content = content || '';\n level = level || 'info';\n this.$el.addClass( 'has-message-box' );\n this.$( '#messagebox' )\n .attr( 'class', 'panel-' + level + '-message' )\n .html( content )\n .toggle( !!content );\n return this;\n },\n\n /** */\n inactivityWarning : function( content ){\n var verificationLink = 'Resend verification.';\n this.$el.addClass( 'has-inactivity-box' );\n this.$( '#inactivebox' )\n .html( content )\n .append( ' ' + verificationLink )\n .toggle( !!content );\n return this;\n },\n\n /** */\n renderPanels : function(){\n var page = this;\n // TODO: Remove this line after select2 update\n $( '.select2-hidden-accessible' ).remove();\n this._panelIds.forEach( function( panelId ){\n if( _.has( page, panelId ) ){\n var panelView = page[ panelId ];\n panelView.setElement( '#' + panelId );\n panelView.render();\n }\n });\n return this;\n },\n\n /** body template */\n //TODO: to underscore\n template: function( options ){\n //TODO: remove inline styling\n return [\n '