"}});return{View:e}}.apply(e,n),!(void 0!==s&&(t.exports=s))}).call(e,i(3),i(1))},function(t,e,i){var n,s;(function(i,o,a){n=[],s=function(){var t=i.Model.extend({defaults:{title:"",icon_class:"",on_click:null,menu_options:null,is_menu_button:!0,id:null,href:null,target:null,enabled:!0,visible:!0,tooltip_config:{}}}),e=i.View.extend({initialize:function(){this.model.attributes.tooltip_config={placement:"bottom"},this.model.bind("change",this.render,this)},render:function(){this.$el.tooltip("hide");var t=this.template(this.model.toJSON());return t.tooltip(this.model.get("tooltip_config")),this.$el.replaceWith(t),this.setElement(t),this},events:{click:"click"},click:function(t){return o.isFunction(this.model.get("on_click"))?(this.model.get("on_click")(t),!1):!0},template:function(t){var e='title="'+t.title+'" class="icon-button';return t.is_menu_button&&(e+=" menu-button"),e+=" "+t.icon_class,t.enabled||(e+="_disabled"),e+='"',t.id&&(e+=' id="'+t.id+'"'),e+=' href="'+t.href+'"',t.target&&(e+=' target="'+t.target+'"'),t.visible||(e+=' style="display: none;"'),e=t.enabled?"":"",a(e)}}),n=i.Collection.extend({model:t}),s=i.View.extend({tagName:"div",initialize:function(){this.render()},render:function(){var t=this;return this.collection.each(function(e){var i=a("").attr("href","javascript:void(0)").attr("title",e.attributes.title).addClass("icon-button menu-button").addClass(e.attributes.icon_class).appendTo(t.$el).click(e.attributes.on_click);e.attributes.tooltip_config&&i.tooltip(e.attributes.tooltip_config);var n=e.get("options");n&&make_popupmenu(i,n)}),this}}),r=function(e,i){i||(i={});var a=new n(o.map(e,function(e){return new t(o.extend(e,i))}));return new s({collection:a})};return{IconButton:t,IconButtonView:e,IconButtonCollection:n,IconButtonMenuView:s,create_icon_buttons_menu:r}}.apply(e,n),!(void 0!==s&&(t.exports=s))}).call(e,i(3),i(2),i(1))},function(t,e,i){var n,s,o;!function(a){s=[i(1)],n=a,o="function"==typeof n?n.apply(e,s):n,!(void 0!==o&&(t.exports=o))}(function(t){"use_strict";var e=t;e.fn.make_text_editable=function(t){var i="num_cols"in t?t.num_cols:30,n="num_rows"in t?t.num_rows:4,s="use_textarea"in t?t.use_textarea:!1,o="on_finish"in t?t.on_finish:null,a="help_text"in t?t.help_text:null,r=e(this);return r.addClass("editable-text").click(function(a){if(!(e(this).children(":input").length>0)){r.removeClass("editable-text");var l,c,d=function(t){r.find(":input").remove(),""!==t?r.text(t):r.html(" "),r.addClass("editable-text"),o&&o(t)},h="cur_text"in t?t.cur_text:r.text();s?(l=e("").attr({rows:n,cols:i}).text(e.trim(h)).keyup(function(t){27===t.keyCode&&d(h)}),c=e("").text("Done").click(function(){return d(l.val()),!1})):l=e("").attr({value:e.trim(h),size:i}).blur(function(){d(h)}).keyup(function(t){27===t.keyCode?e(this).trigger("blur"):13===t.keyCode&&d(e(this).val()),t.stopPropagation()}),r.text(""),r.append(l),c&&r.append(c),l.focus(),l.select(),a.stopPropagation()}}),a&&r.attr("title",a).tooltip(),r}})},function(t,e,i){var n;(function(s,o){n=function(){function t(t){t+="";for(var e=/(\d+)(\d{3})/;e.test(t);)t=t.replace(e,"$1,$2");return t}var e=function(t){return"promise"in t},i=s.Model.extend({defaults:{ajax_settings:{},interval:1e3,success_fn:function(t){return!0}},go:function(){var t=o.Deferred(),e=this,i=e.get("ajax_settings"),n=e.get("success_fn"),s=e.get("interval"),a=function(){o.ajax(i).success(function(e){n(e)?t.resolve(e):setTimeout(a,s)})};return a(),t}}),n=function(t){t||(t="#ffffff"),"string"==typeof t&&(t=[t]);for(var e=0;e>16,s=(65280&i)>>8,o=255&i,d=p(n,s,o),f=!0,e=0;e>16,l=(65280&a)>>8,c=255&a,h=p(r,l,c),u=g(n,s,o,r,l,c),Math.abs(d-h)<40||200>u){f=!1;break}m++}while(!f&&10>=m);return"#"+(16777216+i).toString(16).substr(1,6)};return{commatize:t,is_deferred:e,ServerStateDeferred:i,get_random_color:n}}.call(e,i,e,t),!(void 0!==n&&(t.exports=n))}).call(e,i(3),i(1))},function(t,e,i){var n,s;(function(o,a){n=[i(27),i(6),i(5)],s=function(t,e,i){t=t||window.BibtexParser;var n="citation",s=o.Model.extend(e.LoggableMixin).extend({_logNamespace:n,defaults:{content:""},initialize:function(){var e;try{e=t(this.attributes.content)}catch(i){return}if(e.errors.length){var n=e.errors.reduce(function(t,e){return t+"; "+e});this.log("Error parsing bibtex: "+n)}if(this._fields={},this.entry=a.first(e.entries),this.entry){var s=this.entry.Fields;for(var o in s){var r=s[o],l=o.toLowerCase();this._fields[l]=r}}},entryType:function(){return this.entry?this.entry.EntryType:void 0},fields:function(){return this._fields}}),r=o.Collection.extend(e.LoggableMixin).extend({_logNamespace:n,urlRoot:Galaxy.root+"api",partial:!0,model:s}),l=r.extend({url:function(){return this.urlRoot+"/histories/"+this.history_id+"/citations"}}),c=r.extend({url:function(){return this.urlRoot+"/tools/"+this.tool_id+"/citations"},partial:!1});return{Citation:s,HistoryCitationCollection:l,ToolCitationCollection:c}}.apply(e,n),!(void 0!==s&&(t.exports=s))}).call(e,i(3),i(2))},function(t,e,i){var n,s;(function(o,a){n=[i(4),i(7),i(43)],s=function(t,e,i){var n=i.extend({initialize:function(n){var s=this;i.prototype.initialize.call(this,t.merge({customize:function(t){t.buttons={execute:execute_btn=new e.Button({icon:"fa-check",tooltip:"Execute: "+t.name+" ("+t.version+")",title:"Execute",cls:"ui-button btn btn-primary",floating:"clear",onclick:function(){execute_btn.wait(),s.portlet.disable(),s.submit(t,function(){execute_btn.unwait(),s.portlet.enable()})}})},t.job_id&&t.job_remap&&(t.inputs.rerun_remap_job_id={label:"Resume dependencies from this job",name:"rerun_remap_job_id",type:"select",display:"radio",ignore:"__ignore__",value:"__ignore__",options:[["Yes",t.job_id],["No","__ignore__"]],help:"The previous run of this tool failed and other tools were waiting for it to finish successfully. Use this option to resume those tools using the new output(s) of this tool run."})}},n))},submit:function(e,i){var n=this,s={tool_id:e.id,tool_version:e.version,inputs:this.data.create()};if(this.trigger("reset"),!n.validate(s))return Galaxy.emit.debug("tool-form::submit()","Submission canceled. Validation failed."),void(i&&i());if(e.action!==Galaxy.root+"tool_runner/index"){var r=o("").attr({action:e.action,method:e.method,enctype:e.enctype});return a.each(s.inputs,function(t,e){r.append(o("").attr({name:e,value:t}))}),r.hide().appendTo("body").submit().remove(),void(i&&i())}Galaxy.emit.debug("tool-form::submit()","Validation complete.",s),t.request({type:"POST",url:Galaxy.root+"api/tools",data:s,success:function(t){i&&i(),n.$el.empty().append(n._templateSuccess(t)),parent.Galaxy&&parent.Galaxy.currHistoryPanel&&parent.Galaxy.currHistoryPanel.refreshContents();
},error:function(t){if(i&&i(),Galaxy.emit.debug("tool-form::submit","Submission failed.",t),t&&t.err_data){var e=n.data.matchResponse(t.err_data);for(var o in e){n.highlight(o,e[o]);break}}else{var a=parent.Galaxy.modal;a&&a.show({title:"Job submission failed",body:t&&t.err_msg||n._templateError(s),buttons:{Close:function(){a.hide()}}})}}})},validate:function(t){var e=t.inputs,i=-1,n=null;for(var s in e){var o=e[s],a=this.data.match(s),r=this.field_list[a],l=this.input_list[a];if(a&&l&&r){if(!l.optional&&null==o)return this.highlight(a),!1;if(o&&o.batch){var c=o.values.length,d=c>0&&o.values[0]&&o.values[0].src;if(d)if(null===n)n=d;else if(n!==d)return this.highlight(a,"Please select either dataset or dataset list fields for all batch mode fields."),!1;if(-1===i)i=c;else if(i!==c)return this.highlight(a,"Please make sure that you select the same number of inputs for all batch mode fields. This field contains "+c+" selection(s) while a previous field contains "+i+"."),!1}}else Galaxy.emit.debug("tool-form::validate()","Retrieving input objects failed.")}return!0},_templateSuccess:function(t){if(t.jobs&&t.jobs.length>0){var e=t.jobs.length,i=1==e?"1 job has":e+" jobs have",n=o("").addClass("donemessagelarge").append(o("").text(i+" been successfully added to the queue - resulting in the following datasets:"));return a.each(t.outputs,function(t){n.append(o("").addClass("messagerow").append(o("").text(t.hid+": "+t.name)))}),n.append(o("").append("").text("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}return this._templateError(t)},_templateError:function(t){return o("").addClass("errormessagelarge").append(o("").text("The server could not complete the request. Please contact the Galaxy Team if this error persists.")).append(o("").text(JSON.stringify(t,null,4)))}});return{View:n}}.apply(e,n),!(void 0!==s&&(t.exports=s))}).call(e,i(1),i(2))},function(t,e,i){var n,s;(function(o,a){n=[i(4),i(9),i(7)],s=function(t,e,i){var n=o.View.extend({initialize:function(t){var n=this;this.options=t,this.name=t.name||"element",this.multiple=t.multiple||!1,this.message=new i.Message,this.portlet=new e.View({cls:"ui-portlet-section"}),this.select=new i.Select.View({optional:t.optional}),this.button=new i.ButtonIcon({icon:"fa fa-sign-in",floating:"left",tooltip:"Insert new "+this.name,onclick:function(){n.add({id:n.select.value(),name:n.select.text()})}}),this.setElement(this._template(t)),this.$(".ui-list-message").append(this.message.$el),this.$(".ui-list-portlet").append(this.portlet.$el),this.$(".ui-list-button").append(this.button.$el),this.$(".ui-list-select").append(this.select.$el)},value:function(t){if(void 0!==t){if(this.portlet.empty(),a.isArray(t))for(var e in t){var i=t[e],n=null,s=null;"string"!=a.type(i)?(n=i.id,s=i.name):n=s=i,null!=n&&this.add({id:n,name:s})}this._refresh()}var o=[];return this.$(".ui-list-id").each(function(){o.push({id:a(this).prop("id"),name:a(this).find(".ui-list-name").html()})}),0==o.length?null:o},add:function(e){var i=this;if(0===this.$('[id="'+e.id+'"]').length)if(t.isEmpty(e.id))this.message.update({message:"Please select a valid "+this.name+".",status:"danger"});else{var n=a(this._templateRow({id:e.id,name:e.name}));n.on("click",function(){n.remove(),i._refresh()}),n.on("mouseover",function(){n.addClass("portlet-highlight")}),n.on("mouseout",function(){n.removeClass("portlet-highlight")}),this.portlet.append(n),this._refresh()}else this.message.update({message:"This "+this.name+" is already in the list."})},update:function(t){this.select.update(t)},_refresh:function(){this.$(".ui-list-id").length>0?(!this.multiple&&this.button.disable(),this.$(".ui-list-portlet").show()):(this.button.enable(),this.$(".ui-list-portlet").hide()),this.options.onchange&&this.options.onchange()},_template:function(t){return'
',"Warning: This is a experimental feature. Most Galaxy tools will not annotate"," citations explicitly at this time. When writing up your analysis, please manually"," review your histories and find all references"," that should be cited in order to completely describe your work. Also, please remember to",' cite Galaxy.',"
"],"element");return o.extend({},e.DatasetListItemView.prototype.templates,{titleBar:t})}();var u=c.extend({className:c.prototype.className+" dataset-collection-element",_swapNewRender:function(t){c.prototype._swapNewRender.call(this,t);var e=this.model.get("state")||"ok";return this.$el.addClass("state-"+e),this.$el},toString:function(){var t=this.model?this.model+"":"(no model)";return"NestedDCDCEListItemView("+t+")"}});return{DCListItemView:c,DCEListItemView:d,DatasetDCEListItemView:h,NestedDCDCEListItemView:u}}.apply(e,n),!(void 0!==s&&(t.exports=s))}).call(e,i(2),i(1),i(1))},function(t,e,i){var n,s;(function(o,a,r){n=[i(70),i(6),i(5)],s=function(t,e,i){"use strict";var n="collections",s={defaults:{model_class:"DatasetCollectionElement",element_identifier:null,element_index:null,element_type:null},_mergeObject:function(t){return o.extend(t,t.object,{element_id:t.id}),delete t.object,t},constructor:function(t,e){t=this._mergeObject(t),this.idAttribute="element_id",a.Model.apply(this,arguments)},parse:function(t,e){var i=t;return i=this._mergeObject(i)}},l=a.Model.extend(e.LoggableMixin).extend(s).extend({_logNamespace:n}),c=a.Collection.extend(e.LoggableMixin).extend({_logNamespace:n,model:l,initialize:function(t,e){this.debug(this+"(DCECollection).initialize:",t,e),e=e||{}},toString:function(){return["DatasetCollectionElementCollection(",this.length,")"].join("")}}),d=t.DatasetAssociation.extend(e.mixin(s,{url:function(){return this.has("history_id")?Galaxy.root+"api/histories/"+this.get("history_id")+"/contents/"+this.get("id"):(console.warn("no endpoint for non-hdas within a collection yet"),Galaxy.root+"api/datasets")},defaults:o.extend({},t.DatasetAssociation.prototype.defaults,s.defaults),constructor:function(t,e){this.debug(" DatasetDCE.constructor:",t,e),s.constructor.call(this,t,e)},initialize:function(e,i){this.debug(this+"(DatasetDCE).initialize:",e,i),t.DatasetAssociation.prototype.initialize.call(this,e,i)},hasDetails:function(){return o.has(this.attributes,"annotation")},toString:function(){var t=this.get("element_identifier");return["DatasetDCE(",t,")"].join("")}})),h=c.extend({model:d,initialize:function(t,e){this.debug(this+"(DatasetDCECollection).initialize:",t,e),c.prototype.initialize.call(this,t,e)},toString:function(){return["DatasetDCECollection(",this.length,")"].join("")}}),u=a.Model.extend(e.LoggableMixin).extend(e.SearchableModelMixin).extend({_logNamespace:n,defaults:{collection_type:null,deleted:!1},collectionClass:c,initialize:function(t,e){this.debug(this+"(DatasetCollection).initialize:",t,e,this),this.elements=this._createElementsModel(),this.on("change:elements",function(){this.log("change:elements"),this.elements=this._createElementsModel()})},_createElementsModel:function(){this.debug(this+"._createElementsModel",this.collectionClass,this.get("elements"),this.elements);var t=this.get("elements")||[];return this.unset("elements",{silent:!0}),this.elements=new this.collectionClass(t),this.elements},toJSON:function(){var t=a.Model.prototype.toJSON.call(this);return this.elements&&(t.elements=this.elements.toJSON()),t},inReadyState:function(){var t=this.get("populated");return this.isDeletedOrPurged()||t},hasDetails:function(){return this.debug("hasDetails:",this.elements.length),0!==this.elements.length},getVisibleContents:function(t){return this.elements},"delete":function(t){return this.get("deleted")?r.when():this.save({deleted:!0},t)},undelete:function(t){return!this.get("deleted")||this.get("purged")?r.when():this.save({deleted:!1},t)},isDeletedOrPurged:function(){return this.get("deleted")||this.get("purged")},searchAttributes:["name"],toString:function(){var t=[this.get("id"),this.get("name")||this.get("element_identifier")];return"DatasetCollection("+t.join(",")+")"}}),p=u.extend({collectionClass:h,initialize:function(t,e){this.debug(this+"(ListDatasetCollection).initialize:",t,e),u.prototype.initialize.call(this,t,e)},toString:function(){return["ListDatasetCollection(",this.get("name"),")"].join("")}}),g=p.extend({initialize:function(t,e){this.debug(this+"(PairDatasetCollection).initialize:",t,e),p.prototype.initialize.call(this,t,e)},toString:function(){return["PairDatasetCollection(",this.get("name"),")"].join("")}}),f=u.extend(e.mixin(s,{constructor:function(t,e){this.debug(" NestedDCDCE.constructor:",t,e),s.constructor.call(this,t,e)},toString:function(){var t=this.object?""+this.object:this.get("element_identifier");return["NestedDCDCE(",t,")"].join("")}})),m=c.extend({model:f,initialize:function(t,e){this.debug(this+"(NestedDCDCECollection).initialize:",t,e),c.prototype.initialize.call(this,t,e)},toString:function(){return["NestedDCDCECollection(",this.length,")"].join("")}}),v=g.extend(e.mixin(s,{constructor:function(t,e){this.debug(" NestedPairDCDCE.constructor:",t,e),s.constructor.call(this,t,e)},toString:function(){var t=this.object?""+this.object:this.get("element_identifier");return["NestedPairDCDCE(",t,")"].join("")}})),_=m.extend({model:v,initialize:function(t,e){this.debug(this+"(NestedPairDCDCECollection).initialize:",t,e),m.prototype.initialize.call(this,t,e)},toString:function(){return["NestedPairDCDCECollection(",this.length,")"].join("")}}),y=u.extend({collectionClass:_,initialize:function(t,e){this.debug(this+"(ListPairedDatasetCollection).initialize:",t,e),u.prototype.initialize.call(this,t,e)},toString:function(){return["ListPairedDatasetCollection(",this.get("name"),")"].join("")}});return{ListDatasetCollection:p,PairDatasetCollection:g,ListPairedDatasetCollection:y}}.apply(e,n),!(void 0!==s&&(t.exports=s))}).call(e,i(2),i(3),i(1))},function(t,e,i){var n,s;(function(o,a,r,l){n=[i(39),i(10),i(6),i(8),i(84),i(5),i(81)],s=function(t,e,i,n,s,c){"use strict";function d(t){var e=t.toJSON(),i=f(e,{creationFn:function(e,i){return e=e.map(function(t){return{id:t.id,name:t.name,src:"dataset"===t.history_content_type?"hda":"hdca"}}),t.createHDCA(e,"list",i)}});return i}var h="collections",u=o.View.extend(i.LoggableMixin).extend({_logNamespace:h,tagName:"li",className:"collection-element",initialize:function(t){this.element=t.element||{},this.selected=t.selected||!1},render:function(){return this.$el.attr("data-element-id",this.element.id).attr("draggable",!0).html(this.template({element:this.element})),this.selected&&this.$el.addClass("selected"),this},template:a.template(['',"<%- element.name %>","",'"].join("")),select:function(t){this.$el.toggleClass("selected",t),this.trigger("select",{source:this,selected:this.$el.hasClass("selected")})},discard:function(){var t=this,e=this.$el.parent().width();this.$el.animate({"margin-right":e},"fast",function(){t.trigger("discard",{source:t}),t.destroy()})},destroy:function(){this.off(),this.$el.remove()},events:{click:"_click","click .name":"_clickName","click .discard":"_clickDiscard",dragstart:"_dragstart",dragend:"_dragend",dragover:"_sendToParent",drop:"_sendToParent"},_click:function(t){t.stopPropagation(),this.select(t)},_clickName:function(t){t.stopPropagation(),t.preventDefault();var e=([c("Enter a new name for the element"),":\n(",c("Note that changing the name here will not rename the dataset"),")"].join(""),prompt(c("Enter a new name for the element")+":",this.element.name));e&&(this.element.name=e,this.render())},_clickDiscard:function(t){t.stopPropagation(),this.discard()},_dragstart:function(t){t.originalEvent&&(t=t.originalEvent),t.dataTransfer.effectAllowed="move",t.dataTransfer.setData("text/plain",JSON.stringify(this.element)),this.$el.addClass("dragging"),this.$el.parent().trigger("collection-element.dragstart",[this])},_dragend:function(t){this.$el.removeClass("dragging"),this.$el.parent().trigger("collection-element.dragend",[this])},_sendToParent:function(t){this.$el.parent().trigger(t)},toString:function(){return"DatasetCollectionElementView()"}}),p=o.View.extend(i.LoggableMixin).extend({_logNamespace:h,elementViewClass:u,collectionClass:t.HistoryListDatasetCollection,className:"list-collection-creator collection-creator flex-row-container",minElements:1,defaultAttributes:{creationFn:function(){throw new TypeError("no creation fn for creator")},oncreate:function(){},oncancel:function(){},autoscrollDist:24,highlightClr:"rgba( 64, 255, 255, 1.0 )"},initialize:function(t){this.metric("ListCollectionCreator.initialize",t);var e=this;a.each(this.defaultAttributes,function(i,n){i=t[n]||i,e[n]=i}),e.initialElements=t.elements||[],this._instanceSetUp(),this._elementsSetUp(),this._setUpBehaviors()},_instanceSetUp:function(){this.selectedIds={},this.$dragging=null,this.blocking=!1},_elementsSetUp:function(){this.invalidElements=[],this.workingElements=[],this.elementViews=[],this.workingElements=this.initialElements.slice(0),this._ensureElementIds(),this._validateElements(),this._mangleDuplicateNames(),this._sortElements()},_ensureElementIds:function(){return this.workingElements.forEach(function(t){t.hasOwnProperty("id")||(t.id=a.uniqueId())}),this.workingElements},_validateElements:function(){var t=this;return t.invalidElements=[],this.workingElements=this.workingElements.filter(function(e){var i=t._isElementInvalid(e);return i&&t.invalidElements.push({element:e,text:i}),!i}),this.workingElements},_isElementInvalid:function(t){return"dataset"!==t.history_content_type?c("is not a dataset"):t.state!==e.OK?c(a.contains(e.NOT_READY_STATES,t.state)?"hasn't finished running yet":"has errored, is paused, or is not accessible"):t.deleted||t.purged?c("has been deleted or purged"):null},_mangleDuplicateNames:function(){var t=900,e=1,i={};this.workingElements.forEach(function(n){for(var s=n.name;i.hasOwnProperty(s);)if(s=n.name+" ("+e+")",e+=1,e>=t)throw new Error("Safety hit in while loop - thats impressive");n.name=s,i[n.name]=!0})},_sortElements:function(t){},render:function(t,e){return this.workingElements.length .clear-selected").show():this.$(".collection-elements-controls > .clear-selected").hide()},_renderList:function(t,e){var i=this,n=l(""),s=i.$list();a.each(this.elementViews,function(t){t.destroy(),i.removeElementView(t)}),i.workingElements.forEach(function(t){var e=i._createElementView(t);n.append(e.$el)}),i._renderClearSelected(),s.empty().append(n.children()),a.invoke(i.elementViews,"render"),s.height()>s.css("max-height")?s.css("border-width","1px 0px 1px 0px"):s.css("border-width","0px")},_createElementView:function(t){var e=new this.elementViewClass({element:t,selected:a.has(this.selectedIds,t.id)});return this.elementViews.push(e),this._listenToElementView(e),e},_listenToElementView:function(t){var e=this;e.listenTo(t,{select:function(t){var i=t.source.element;t.selected?e.selectedIds[i.id]=!0:delete e.selectedIds[i.id],e.trigger("elements:select",t)},discard:function(t){e.trigger("elements:discard",t)}})},addElementView:function(t){},removeElementView:function(t){delete this.selectedIds[t.element.id],this._renderClearSelected(),this.elementViews=a.without(this.elementViews,t),this.stopListening(t)},_renderNoElementsLeft:function(){this._disableNameAndCreate(!0),this.$(".collection-elements").append(this.templates.noElementsLeft())},_elementToJSON:function(t){return t},createList:function(t){if(!this.workingElements.length){var e=c("No valid elements for final list")+". ";return e+=''+c("Cancel")+" ",e+=c("or"),e+=' '+c("start over")+".",void this._showAlert(e)}var i=this,n=this.workingElements.map(function(t){return i._elementToJSON(t)});return i.blocking=!0,i.creationFn(n,t).always(function(){i.blocking=!1}).fail(function(t,e,n){i.trigger("error",{xhr:t,status:e,message:c("An error occurred while creating this collection")})}).done(function(t,e,n){i.trigger("collection:created",t,e,n),i.metric("collection:created",t),"function"==typeof i.oncreate&&i.oncreate.call(this,t,e,n)})},_setUpBehaviors:function(){return this.on("error",this._errorHandler),this.once("rendered",function(){this.trigger("rendered:initial",this)}),this.on("elements:select",function(t){this._renderClearSelected()}),this.on("elements:discard",function(t){var e=t.source.element;this.removeElementView(t.source),this.workingElements=a.without(this.workingElements,e),this.workingElements.length||this._renderNoElementsLeft()}),this},_errorHandler:function(t){this.error(t);var e=this;if(content=t.message||c("An error occurred"),t.xhr){var i=t.xhr,n=t.message;0===i.readyState&&0===i.status?content+=": "+c("Galaxy could not be reached and may be updating.")+c(" Try again in a few minutes."):i.responseJSON?content+=":
",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(["Rename elements in the list by clicking on ",'the existing name.'].join("")),"
","
",c(["Discard elements from the final created list by clicking on the ",'"Discard" button.'].join("")),"
","
",c(["Reorder the list by clicking and dragging elements. Select multiple elements by clicking on ",'them and you can then move those selected by dragging the ',"entire group. Deselect them by clicking them again or by clicking the ",'the "Clear selected" link.'].join("")),"
","
",c(['Click the "Start over" link to begin again as if you had just opened ',"the interface."].join("")),"
","
",c(['Click the "Cancel" button to exit the interface.'].join("")),"
","
","
",c(['Once your collection is complete, enter a name and ','click "Create list".'].join("")),"
"].join("")),invalidElements:a.template([c("The following selections could not be included due to problems:"),"
','',"<% if( _.size( problems ) ){ %>",c("The following selections could not be included due to problems"),":","
<% _.each( problems, function( problem ){ %>","
<%- problem.element.name %>: <%- problem.text %>
","<% }); %>
","<% } else if( _.size( elements ) < 1 ){ %>",c("No datasets were selected"),".","<% } %>"," ",c("At least one element is needed for the collection"),". ",c("You may need to "),'',c("cancel")," ",c("and reselect new elements"),".","","
","
",'"].join(""))},toString:function(){return"ListCollectionCreator"}}),g=function(t,e,i){var s,o=l.Deferred(),r=Galaxy.modal||new n.View;return e=a.defaults(e||{},{elements:t,oncancel:function(){r.hide(),o.reject("cancelled")},oncreate:function(t,e){r.hide(),o.resolve(e)}}),s=new i(e),r.show({title:e.title||c("Create a collection"),body:s.$el,width:"80%",height:"100%",closing_events:!0}),s.render(),window._collectionCreator=s,o},f=function(t,e){return e=e||{},e.title=c("Create a collection from a list of datasets"),g(t,e,p)};return{DatasetCollectionElementView:u,ListCollectionCreator:p,collectionCreatorModal:g,listCollectionCreatorModal:f,createListCollection:d}}.apply(e,n),!(void 0!==s&&(t.exports=s))}).call(e,i(3),i(2),i(1),i(1))},function(t,e,i){var n,s;(function(o,a,r,l){n=[i(42),i(10),i(23),i(6),i(5)],s=function(t,e,i,n,s){
"use strict";var c="dataset",d=t.ListItemView,h=d.extend({_logNamespace:c,className:d.prototype.className+" dataset",id:function(){return["dataset",this.model.get("id")].join("-")},initialize:function(t){t.logger&&(this.logger=this.model.logger=t.logger),this.log(this+".initialize:",t),d.prototype.initialize.call(this,t),this.linkTarget=t.linkTarget||"_blank"},_setUpListeners:function(){d.prototype._setUpListeners.call(this),this.listenTo(this.model,"change",function(t,e){this.model.changedAttributes().state&&this.model.inReadyState()&&this.expanded&&!this.model.hasDetails()?this.model.fetch():this.render()})},_fetchModelDetails:function(){var t=this;return t.model.inReadyState()&&!t.model.hasDetails()?t.model.fetch({silent:!0}):o.when()},remove:function(t,e){var i=this;t=t||this.fxSpeed,this.$el.fadeOut(t,function(){a.View.prototype.remove.call(i),e&&e.call(i)})},render:function(t){return d.prototype.render.call(this,t)},_swapNewRender:function(t){return d.prototype._swapNewRender.call(this,t),this.model.has("state")&&this.$el.addClass("state-"+this.model.get("state")),this.$el},_renderPrimaryActions:function(){return[this._renderDisplayButton()]},_renderDisplayButton:function(){var t=this.model.get("state");if(t===e.NOT_VIEWABLE||t===e.DISCARDED||!this.model.get("accessible"))return null;var n={target:this.linkTarget,classes:"display-btn"};if(this.model.get("purged"))n.disabled=!0,n.title=s("Cannot display datasets removed from disk");else if(t===e.UPLOAD)n.disabled=!0,n.title=s("This dataset must finish uploading before it can be viewed");else if(t===e.NEW)n.disabled=!0,n.title=s("This dataset is not yet viewable");else{n.title=s("View data"),n.href=this.model.urls.display;var o=this;n.onclick=function(t){Galaxy.frame&&Galaxy.frame.active&&(Galaxy.frame.addDataset(o.model.get("id")),t.preventDefault())}}return n.faIcon="fa-eye",i(n)},_renderDetails:function(){if(this.model.get("state")===e.NOT_VIEWABLE)return r(this.templates.noAccess(this.model.toJSON(),this));var t=d.prototype._renderDetails.call(this);return t.find(".actions .left").empty().append(this._renderSecondaryActions()),t.find(".summary").html(this._renderSummary()).prepend(this._renderDetailMessages()),t.find(".display-applications").html(this._renderDisplayApplications()),this._setUpBehaviors(t),t},_renderSummary:function(){var t=this.model.toJSON(),e=this.templates.summaries[t.state];return(e=e||this.templates.summaries.unknown)(t,this)},_renderDetailMessages:function(){var t=this,e=r(''),i=t.model.toJSON();return l.each(t.templates.detailMessages,function(n){e.append(r(n(i,t)))}),e},_renderDisplayApplications:function(){return this.model.isDeletedOrPurged()?"":[this.templates.displayApplications(this.model.get("display_apps"),this),this.templates.displayApplications(this.model.get("display_types"),this)].join("")},_renderSecondaryActions:function(){switch(this.debug("_renderSecondaryActions"),this.model.get("state")){case e.NOT_VIEWABLE:return[];case e.OK:case e.FAILED_METADATA:case e.ERROR:return[this._renderDownloadButton(),this._renderShowParamsButton()]}return[this._renderShowParamsButton()]},_renderShowParamsButton:function(){return i({title:s("View details"),classes:"params-btn",href:this.model.urls.show_params,target:this.linkTarget,faIcon:"fa-info-circle"})},_renderDownloadButton:function(){return this.model.get("purged")||!this.model.hasData()?null:l.isEmpty(this.model.get("meta_files"))?r(['','',""].join("")):this._renderMetaFileDownloadButton()},_renderMetaFileDownloadButton:function(){var t=this.model.urls;return r(['
');\n\t }\n\t }\n\t\n\t // Render first chunk.\n\t var self = this,\n\t first_chunk = this.model.get('first_data_chunk');\n\t if (first_chunk) {\n\t // First chunk is bootstrapped, so render now.\n\t this._renderChunk(first_chunk);\n\t }\n\t else {\n\t // No bootstrapping, so get first chunk and then render.\n\t $.when(self.model.get_next_chunk()).then(function(result) {\n\t self._renderChunk(result);\n\t });\n\t }\n\t\n\t // -- Show new chunks during scrolling. --\n\t\n\t // Set up chunk loading when scrolling using the scrolling element.\n\t this.scroll_elt.scroll(function(){\n\t self.attempt_to_fetch();\n\t });\n\t },\n\t\n\t /**\n\t * Returns true if user has scrolled to the bottom of the view.\n\t */\n\t scrolled_to_bottom: function() {\n\t return false;\n\t },\n\t\n\t // -- Helper functions. --\n\t\n\t _renderCell: function(cell_contents, index, colspan) {\n\t var $cell = $('
').text(cell_contents);\n\t var column_types = this.model.get_metadata('column_types');\n\t if (colspan !== undefined) {\n\t $cell.attr('colspan', colspan).addClass('stringalign');\n\t } else if (column_types) {\n\t if (index < column_types.length) {\n\t if (column_types[index] === 'str' || column_types[index] === 'list') {\n\t /* Left align all str columns, right align the rest */\n\t $cell.addClass('stringalign');\n\t }\n\t }\n\t }\n\t return $cell;\n\t },\n\t\n\t _renderRow: function(line) {\n\t // Check length of cells to ensure this is a complete row.\n\t var cells = line.split('\\t'),\n\t row = $('
'),\n\t num_columns = this.model.get_metadata('columns');\n\t\n\t if (this.row_count % 2 !== 0) {\n\t row.addClass('dark_row');\n\t }\n\t\n\t if (cells.length === num_columns) {\n\t _.each(cells, function(cell_contents, index) {\n\t row.append(this._renderCell(cell_contents, index));\n\t }, this);\n\t }\n\t else if (cells.length > num_columns) {\n\t // SAM file or like format with optional metadata included.\n\t _.each(cells.slice(0, num_columns - 1), function(cell_contents, index) {\n\t row.append(this._renderCell(cell_contents, index));\n\t }, this);\n\t row.append(this._renderCell(cells.slice(num_columns - 1).join('\\t'), num_columns - 1));\n\t }\n\t else if (num_columns > 5 && cells.length === num_columns - 1 ) {\n\t // SAM file or like format with optional metadata missing.\n\t _.each(cells, function(cell_contents, index) {\n\t row.append(this._renderCell(cell_contents, index));\n\t }, this);\n\t row.append($('
'));\n\t }\n\t else {\n\t // Comment line, just return the one cell.\n\t row.append(this._renderCell(line, 0, num_columns));\n\t }\n\t\n\t this.row_count++;\n\t return row;\n\t },\n\t\n\t _renderChunk: function(chunk) {\n\t var data_table = this.$el.find('table');\n\t _.each(chunk.ck_data.split('\\n'), function(line, index) {\n\t if (line !== ''){\n\t data_table.append(this._renderRow(line));\n\t }\n\t }, this);\n\t }\n\t});\n\t\n\t/**\n\t * Tabular view that is placed at the top level of page. Scrolling occurs\n\t * view top-level elements outside of view.\n\t */\n\tvar TopLevelTabularDatasetChunkedView = TabularDatasetChunkedView.extend({\n\t\n\t initialize: function(options) {\n\t TabularDatasetChunkedView.prototype.initialize.call(this, options);\n\t\n\t // Scrolling happens in top-level elements.\n\t scroll_elt = _.find(this.$el.parents(), function(p) {\n\t return $(p).css('overflow') === 'auto';\n\t });\n\t\n\t // If no scrolling element found, use window.\n\t if (!scroll_elt) { scroll_elt = window; }\n\t\n\t // Wrap scrolling element for easy access.\n\t this.scroll_elt = $(scroll_elt);\n\t },\n\t\n\t /**\n\t * Returns true if user has scrolled to the bottom of the view.\n\t */\n\t scrolled_to_bottom: function() {\n\t return (this.$el.height() - this.scroll_elt.scrollTop() - this.scroll_elt.height() <= 0);\n\t }\n\t\n\t});\n\t\n\t/**\n\t * Tabular view tnat is embedded in a page. Scrolling occurs in view's el.\n\t */\n\tvar EmbeddedTabularDatasetChunkedView = TabularDatasetChunkedView.extend({\n\t\n\t initialize: function(options) {\n\t TabularDatasetChunkedView.prototype.initialize.call(this, options);\n\t\n\t // Because view is embedded, set up div to do scrolling.\n\t this.scroll_elt = this.$el.css({\n\t position: 'relative',\n\t overflow: 'scroll',\n\t height: options.height || '500px'\n\t });\n\t },\n\t\n\t /**\n\t * Returns true if user has scrolled to the bottom of the view.\n\t */\n\t scrolled_to_bottom: function() {\n\t return this.$el.scrollTop() + this.$el.innerHeight() >= this.el.scrollHeight;\n\t }\n\t\n\t});\n\t\n\t// button for trackster visualization\n\tvar TabularButtonTracksterView = Backbone.View.extend({\n\t\n\t // gene region columns\n\t col: {\n\t chrom : null,\n\t start : null,\n\t end : null\n\t },\n\t\n\t // url for trackster\n\t url_viz : null,\n\t\n\t // dataset id\n\t dataset_id : null,\n\t\n\t // database key\n\t genome_build: null,\n\t\n\t // data type\n\t file_ext : null,\n\t\n\t // backbone initialize\n\t initialize: function (options) {\n\t // check if environment is available\n\t var Galaxy = parent.Galaxy;\n\t\n\t // link galaxy modal or create one\n\t if (Galaxy && Galaxy.modal) {\n\t this.modal = Galaxy.modal;\n\t }\n\t\n\t // link galaxy frames\n\t if (Galaxy && Galaxy.frame) {\n\t this.frame = Galaxy.frame;\n\t }\n\t\n\t // check\n\t if (!this.modal || !this.frame) {\n\t return;\n\t }\n\t\n\t // model/metadata\n\t var model = options.model;\n\t var metadata = model.get('metadata');\n\t\n\t // check for datatype\n\t if (!model.get('file_ext')) {\n\t return;\n\t }\n\t\n\t // get data type\n\t this.file_ext = model.get('file_ext');\n\t\n\t // check for bed-file format\n\t if (this.file_ext == 'bed')\n\t {\n\t // verify that metadata exists\n\t if (metadata.get('chromCol') && metadata.get('startCol') && metadata.get('endCol'))\n\t {\n\t // read in columns\n\t this.col.chrom = metadata.get('chromCol') - 1;\n\t this.col.start = metadata.get('startCol') - 1;\n\t this.col.end = metadata.get('endCol') - 1;\n\t } else {\n\t console.log('TabularButtonTrackster : Bed-file metadata incomplete.');\n\t return;\n\t }\n\t }\n\t\n\t // check for vcf-file format\n\t if (this.file_ext == 'vcf')\n\t {\n\t // search array\n\t function search (str, array) {\n\t for (var j = 0; j < array.length; j++)\n\t if (array[j].match(str)) return j;\n\t return -1;\n\t };\n\t\n\t // load\n\t this.col.chrom = search('Chrom', metadata.get('column_names'));\n\t this.col.start = search('Pos', metadata.get('column_names'));\n\t this.col.end = null;\n\t\n\t // verify that metadata exists\n\t if (this.col.chrom == -1 || this.col.start == -1) {\n\t console.log('TabularButtonTrackster : VCF-file metadata incomplete.');\n\t return;\n\t }\n\t }\n\t\n\t // check\n\t if(this.col.chrom === undefined) {\n\t return;\n\t }\n\t\n\t // get dataset id\n\t if (model.id) {\n\t this.dataset_id = model.id;\n\t } else {\n\t console.log('TabularButtonTrackster : Dataset identification is missing.');\n\t return;\n\t }\n\t\n\t // get url\n\t if (model.get('url_viz')) {\n\t this.url_viz = model.get('url_viz');\n\t } else {\n\t console.log('TabularButtonTrackster : Url for visualization controller is missing.');\n\t return;\n\t }\n\t\n\t // get genome_build / database key\n\t if (model.get('genome_build')) {\n\t this.genome_build = model.get('genome_build');\n\t }\n\t\n\t // create the icon\n\t var btn_viz = new mod_icon_btn.IconButtonView({\n\t model : new mod_icon_btn.IconButton({\n\t title : 'Visualize',\n\t icon_class : 'chart_curve',\n\t id : 'btn_viz'\n\t })\n\t });\n\t\n\t // set element\n\t this.setElement(options.$el);\n\t\n\t // add to element\n\t this.$el.append(btn_viz.render().$el);\n\t\n\t // hide the button\n\t this.hide();\n\t },\n\t\n\t // backbone events\n\t events:\n\t {\n\t 'mouseover tr' : 'show',\n\t 'mouseleave' : 'hide'\n\t },\n\t\n\t // show button\n\t show: function (e) {\n\t // is numeric\n\t function is_numeric(n) {\n\t return !isNaN(parseFloat(n)) && isFinite(n);\n\t };\n\t\n\t // check\n\t if(this.col.chrom === null)\n\t return;\n\t\n\t // get selected data line\n\t var row = $(e.target).parent();\n\t\n\t // verify that location has been found\n\t var chrom = row.children().eq(this.col.chrom).html();\n\t var start = row.children().eq(this.col.start).html();\n\t\n\t // end is optional\n\t var end = this.col.end ? row.children().eq(this.col.end).html() : start;\n\t\n\t // double check location\n\t if (!chrom.match(\"^#\") && chrom !== \"\" && is_numeric(start)) {\n\t\n\t // get target gene region\n\t var btn_viz_pars = {\n\t dataset_id : this.dataset_id,\n\t gene_region : chrom + \":\" + start + \"-\" + end\n\t };\n\t\n\t // get button position\n\t var offset = row.offset();\n\t var left = offset.left - 10;\n\t var top = offset.top - $(window).scrollTop() + 3;\n\t\n\t // update css\n\t $('#btn_viz').css({'position': 'fixed', 'top': top + 'px', 'left': left + 'px'});\n\t $('#btn_viz').off('click');\n\t $('#btn_viz').click(this.create_trackster_action(this.url_viz, btn_viz_pars, this.genome_build));\n\t\n\t // show the button\n\t $('#btn_viz').show();\n\t } else {\n\t // hide the button\n\t $('#btn_viz').hide();\n\t }\n\t },\n\t\n\t // hide button\n\t hide: function () {\n\t this.$el.find('#btn_viz').hide();\n\t },\n\t\n\t // create action\n\t create_trackster_action : function (vis_url, dataset_params, dbkey) {\n\t // link this\n\t var self = this;\n\t\n\t // create function\n\t return function() {\n\t var listTracksParams = {};\n\t if (dbkey) {\n\t listTracksParams[ 'f-dbkey' ] = dbkey;\n\t }\n\t $.ajax({\n\t url: vis_url + '/list_tracks?' + $.param( listTracksParams ),\n\t dataType: 'html',\n\t error: function() {\n\t // show error message\n\t self.modal.show({\n\t title : 'Something went wrong!',\n\t body : 'Unfortunately we could not add this dataset to the track browser. Please try again or contact us.',\n\t buttons : {\n\t 'Cancel': function(){\n\t self.modal.hide();\n\t }\n\t }\n\t });\n\t },\n\t success: function(table_html) {\n\t self.modal.show({\n\t title : 'View Data in a New or Saved Visualization',\n\t buttons :{\n\t 'Cancel': function(){\n\t self.modal.hide();\n\t },\n\t 'View in saved visualization': function(){\n\t // show modal with saved visualizations\n\t self.modal.show(\n\t {\n\t title : 'Add Data to Saved Visualization',\n\t body : table_html,\n\t buttons : {\n\t 'Cancel': function(){\n\t self.modal.hide();\n\t },\n\t 'Add to visualization': function(){\n\t // hide\n\t self.modal.hide();\n\t\n\t // search selected fields\n\t self.modal.$el.find('input[name=id]:checked').each(function(){\n\t // get visualization id\n\t var vis_id = $(this).val();\n\t dataset_params.id = vis_id;\n\t\n\t // add widget\n\t self.frame.add({\n\t title : 'Trackster',\n\t type : 'url',\n\t content : vis_url + '/trackster?' + $.param(dataset_params)\n\t });\n\t });\n\t }\n\t }\n\t });\n\t },\n\t 'View in new visualization': function(){\n\t // hide\n\t self.modal.hide();\n\t\n\t // add widget\n\t self.frame.add({\n\t title : 'Trackster',\n\t type : 'url',\n\t content : vis_url + '/trackster?' + $.param(dataset_params)\n\t });\n\t }\n\t }\n\t });\n\t }\n\t });\n\t return false;\n\t };\n\t }\n\t});\n\t\n\t// -- Utility functions. --\n\t\n\t/**\n\t * Create a model, attach it to a view, render view, and attach it to a parent element.\n\t */\n\tvar createModelAndView = function(model, view, model_config, parent_elt) {\n\t // Create model, view.\n\t var a_view = new view({\n\t model: new model(model_config)\n\t });\n\t\n\t // Render view and add to parent element.\n\t a_view.render();\n\t if (parent_elt) {\n\t parent_elt.append(a_view.$el);\n\t }\n\t\n\t return a_view;\n\t};\n\t\n\t/**\n\t * Create a tabular dataset chunked view (and requisite tabular dataset model)\n\t * and appends to parent_elt.\n\t */\n\tvar createTabularDatasetChunkedView = function(options) {\n\t // If no model, create and set model from dataset config.\n\t if (!options.model) {\n\t options.model = new TabularDataset(options.dataset_config);\n\t }\n\t\n\t var parent_elt = options.parent_elt;\n\t var embedded = options.embedded;\n\t\n\t // Clean up options so that only needed options are passed to view.\n\t delete options.embedded;\n\t delete options.parent_elt;\n\t delete options.dataset_config;\n\t\n\t // Create and set up view.\n\t var view = (embedded ? new EmbeddedTabularDatasetChunkedView(options) :\n\t new TopLevelTabularDatasetChunkedView(options));\n\t view.render();\n\t\n\t if (parent_elt) {\n\t parent_elt.append(view.$el);\n\t // If we're sticking this in another element, once it's appended check\n\t // to make sure we've filled enough space.\n\t // Without this, the scroll elements don't work.\n\t view.expand_to_container();\n\t }\n\t\n\t return view;\n\t};\n\t\n\treturn {\n\t Dataset: Dataset,\n\t TabularDataset: TabularDataset,\n\t DatasetCollection: DatasetCollection,\n\t TabularDatasetChunkedView: TabularDatasetChunkedView,\n\t createTabularDatasetChunkedView: createTabularDatasetChunkedView\n\t};\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 13 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $) {/** This class contains all button views.\n\t*/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4)], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils ) {\n\t /** This renders the default button which is used e.g. at the bottom of the upload modal.\n\t */\n\t var ButtonBase = Backbone.View.extend({\n\t initialize: function( options ) {\n\t this.options = Utils.merge( options, {\n\t id : Utils.uid(),\n\t title : '',\n\t floating : 'right',\n\t icon : '',\n\t cls : 'ui-button btn btn-default',\n\t cls_wait : 'btn btn-info'\n\t } );\n\t this.setElement( this._template( this.options ) );\n\t var self = this;\n\t $( this.el ).on( 'click' , function() {\n\t $( '.tooltip' ).hide();\n\t if ( options.onclick && !self.disabled ) {\n\t options.onclick();\n\t }\n\t } );\n\t $( this.el ).tooltip( { title: options.tooltip, placement: 'bottom' } );\n\t },\n\t\n\t // disable\n\t disable: function() {\n\t this.$el.addClass( 'disabled' );\n\t this.disabled = true;\n\t },\n\t\n\t // enable\n\t enable: function() {\n\t this.$el.removeClass( 'disabled' );\n\t this.disabled = false;\n\t },\n\t\n\t // show spinner\n\t wait: function() {\n\t this.$el.removeClass( this.options.cls ).addClass( this.options.cls_wait ).prop( 'disabled', true );\n\t this.$( '.icon' ).removeClass( this.options.icon ).addClass( 'fa-spinner fa-spin' );\n\t this.$( '.title' ).html( 'Sending...' );\n\t },\n\t\n\t // hide spinner\n\t unwait: function() {\n\t this.$el.removeClass( this.options.cls_wait ).addClass( this.options.cls ).prop( 'disabled', false );\n\t this.$( '.icon' ).removeClass( 'fa-spinner fa-spin' ).addClass( this.options.icon );\n\t this.$( '.title' ).html( this.options.title );\n\t },\n\t\n\t // template\n\t _template: function( options ) {\n\t var str = '';\n\t return str;\n\t }\n\t });\n\t\n\t /** This button allows the right-click/open-in-new-tab feature, its used e.g. for panel buttons.\n\t */\n\t var ButtonLink = ButtonBase.extend({\n\t initialize: function( options ) {\n\t ButtonBase.prototype.initialize.call( this, options );\n\t },\n\t _template: function( options ) {\n\t return '' + '' +\n\t '';\n\t }\n\t });\n\t\n\t /** The check button is used in the tool form and allows to distinguish between multiple states e.g. all, partially and nothing selected.\n\t */\n\t var ButtonCheck = Backbone.View.extend({\n\t initialize: function( options ) {\n\t // configure options\n\t this.options = Utils.merge(options, {\n\t title : 'Select/Unselect all',\n\t icons : ['fa fa-square-o', 'fa fa-minus-square-o', 'fa fa-check-square-o'],\n\t value : 0\n\t });\n\t\n\t // create new element\n\t this.setElement( this._template() );\n\t this.$title = this.$( '.title' );\n\t this.$icon = this.$( '.icon' );\n\t\n\t // set initial value\n\t this.value( this.options.value );\n\t\n\t // set title\n\t this.$title.html( this.options.title );\n\t\n\t // add event handler\n\t var self = this;\n\t this.$el.on('click', function() {\n\t self.current = ( self.current === 0 && 2 ) || 0;\n\t self.value( self.current );\n\t self.options.onclick && self.options.onclick();\n\t });\n\t },\n\t\n\t /* Sets a new value and/or returns the current value.\n\t * @param{Integer} new_val - Set a new value 0=unchecked, 1=partial and 2=checked.\n\t * OR:\n\t * @param{Integer} new_val - Number of selected options.\n\t * @param{Integer} total - Total number of available options.\n\t */\n\t value: function ( new_val, total ) {\n\t if ( new_val !== undefined ) {\n\t if ( total ) {\n\t if ( new_val !== 0 ) {\n\t new_val = ( new_val !== total ) && 1 || 2;\n\t }\n\t }\n\t this.current = new_val;\n\t this.$icon.removeClass()\n\t .addClass( 'icon' )\n\t .addClass( this.options.icons[ new_val ] );\n\t this.options.onchange && this.options.onchange( new_val );\n\t }\n\t return this.current;\n\t },\n\t\n\t /** Template containing the check button and the title\n\t */\n\t _template: function() {\n\t return '
',\n\t 'Warning: This is a experimental feature. Most Galaxy tools will not annotate',\n\t ' citations explicitly at this time. When writing up your analysis, please manually',\n\t ' review your histories and find all references',\n\t ' that should be cited in order to completely describe your work. Also, please remember to',\n\t ' cite Galaxy.',\n\t '
'\n\t ], 'collection' );\n\t\n\t // use element identifier\n\t var subtitleTemplate = BASE_MVC.wrapTemplate([\n\t '
',\n\t '<% var countText = collection.element_count? ( collection.element_count + \" \" ) : \"\"; %>',\n\t '<% if( collection.collection_type === \"list\" ){ %>',\n\t _l( 'a list of <%- countText %>datasets' ),\n\t '<% } else if( collection.collection_type === \"paired\" ){ %>',\n\t _l( 'a pair of datasets' ),\n\t '<% } else if( collection.collection_type === \"list:paired\" ){ %>',\n\t _l( 'a list of <%- countText %>dataset pairs' ),\n\t '<% } %>',\n\t '
'\n\t ], 'collection' );\n\t\n\t return _.extend( {}, FoldoutListItemView.prototype.templates, {\n\t warnings : warnings,\n\t titleBar : titleBarTemplate,\n\t subtitle : subtitleTemplate\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t/** @class Read only view for DatasetCollectionElement.\n\t */\n\tvar DCEListItemView = ListItemView.extend(\n\t/** @lends DCEListItemView.prototype */{\n\t//TODO: this might be expendable - compacted with HDAListItemView\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t //logger : console,\n\t\n\t /** add the DCE class to the list item */\n\t className : ListItemView.prototype.className + \" dataset-collection-element\",\n\t\n\t /** set up */\n\t initialize : function( attributes ){\n\t if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; }\n\t this.log( 'DCEListItemView.initialize:', attributes );\n\t ListItemView.prototype.initialize.call( this, attributes );\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'DCEListItemView(' + modelString + ')';\n\t }\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tDCEListItemView.prototype.templates = (function(){\n\t\n\t // use the element identifier here - since that will persist and the user will need it\n\t var titleBarTemplate = BASE_MVC.wrapTemplate([\n\t '
',\n\t '
',\n\t '<%- element.element_identifier %>',\n\t '
',\n\t '',\n\t '
'\n\t ], 'element' );\n\t\n\t return _.extend( {}, ListItemView.prototype.templates, {\n\t titleBar : titleBarTemplate\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t/** @class Read only view for a DatasetCollectionElement that is also an DatasetAssociation\n\t * (a dataset contained in a dataset collection).\n\t */\n\tvar DatasetDCEListItemView = DATASET_LI.DatasetListItemView.extend(\n\t/** @lends DatasetDCEListItemView.prototype */{\n\t\n\t className : DATASET_LI.DatasetListItemView.prototype.className + \" dataset-collection-element\",\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t //logger : console,\n\t\n\t /** set up */\n\t initialize : function( attributes ){\n\t if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; }\n\t this.log( 'DatasetDCEListItemView.initialize:', attributes );\n\t DATASET_LI.DatasetListItemView.prototype.initialize.call( this, attributes );\n\t },\n\t\n\t /** In this override, only get details if in the ready state.\n\t * Note: fetch with no 'change' event triggering to prevent automatic rendering.\n\t */\n\t _fetchModelDetails : function(){\n\t var view = this;\n\t if( view.model.inReadyState() && !view.model.hasDetails() ){\n\t return view.model.fetch({ silent: true });\n\t }\n\t return jQuery.when();\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'DatasetDCEListItemView(' + modelString + ')';\n\t }\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tDatasetDCEListItemView.prototype.templates = (function(){\n\t\n\t // use the element identifier here and not the dataset name\n\t //TODO:?? can we steal the DCE titlebar?\n\t var titleBarTemplate = BASE_MVC.wrapTemplate([\n\t '
',\n\t '',\n\t '
',\n\t '<%- element.element_identifier %>',\n\t '
',\n\t '
'\n\t ], 'element' );\n\t\n\t return _.extend( {}, DATASET_LI.DatasetListItemView.prototype.templates, {\n\t titleBar : titleBarTemplate\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t/** @class Read only view for a DatasetCollectionElement that is also a DatasetCollection\n\t * (a nested DC).\n\t */\n\tvar NestedDCDCEListItemView = DCListItemView.extend(\n\t/** @lends NestedDCDCEListItemView.prototype */{\n\t\n\t className : DCListItemView.prototype.className + \" dataset-collection-element\",\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t // comment this out to suppress log output\n\t //logger : console,\n\t\n\t /** In this override, add the state as a class for use with state-based CSS */\n\t _swapNewRender : function( $newRender ){\n\t DCListItemView.prototype._swapNewRender.call( this, $newRender );\n\t//TODO: model currently has no state\n\t var state = this.model.get( 'state' ) || 'ok';\n\t //if( this.model.has( 'state' ) ){\n\t this.$el.addClass( 'state-' + state );\n\t //}\n\t return this.$el;\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'NestedDCDCEListItemView(' + modelString + ')';\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t DCListItemView : DCListItemView,\n\t DCEListItemView : DCEListItemView,\n\t DatasetDCEListItemView : DatasetDCEListItemView,\n\t NestedDCDCEListItemView : NestedDCDCEListItemView\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__(1), __webpack_require__(1)))\n\n/***/ },\n/* 30 */\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__(70),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( DATASET_MODEL, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'collections';\n\t//==============================================================================\n\t/*\n\tNotes:\n\t\n\tTerminology:\n\t DatasetCollection/DC : a container of datasets or nested DatasetCollections\n\t Element/DatasetCollectionElement/DCE : an item contained in a DatasetCollection\n\t HistoryDatasetCollectionAssociation/HDCA: a DatasetCollection contained in a history\n\t\n\t\n\tThis all seems too complex unfortunately:\n\t\n\t- Terminology collision between DatasetCollections (DCs) and Backbone Collections.\n\t- In the DatasetCollections API JSON, DC Elements use a 'Has A' stucture to *contain*\n\t either a dataset or a nested DC. This would make the hierarchy much taller. I've\n\t decided to merge the contained JSON with the DC element json - making the 'has a'\n\t relation into an 'is a' relation. This seems simpler to me and allowed a lot of\n\t DRY in both models and views, but may make tracking or tracing within these models\n\t more difficult (since DatasetCollectionElements are now *also* DatasetAssociations\n\t or DatasetCollections (nested)). This also violates the rule of thumb about\n\t favoring aggregation over inheritance.\n\t- Currently, there are three DatasetCollection subclasses: List, Pair, and ListPaired.\n\t These each should a) be usable on their own, b) be usable in the context of\n\t nesting within a collection model (at least in the case of ListPaired), and\n\t c) be usable within the context of other container models (like History or\n\t LibraryFolder, etc.). I've tried to separate/extract classes in order to\n\t handle those three situations, but it's proven difficult to do in a simple,\n\t readable manner.\n\t- Ideally, histories and libraries would inherit from the same server models as\n\t dataset collections do since they are (in essence) dataset collections themselves -\n\t making the whole nested structure simpler. This would be a large, error-prone\n\t refactoring and migration.\n\t\n\tMany of the classes and heirarchy are meant as extension points so, while the\n\trelations and flow may be difficult to understand initially, they'll allow us to\n\thandle the growth or flux dataset collection in the future (w/o actually implementing\n\tany YAGNI).\n\t\n\t*/\n\t//_________________________________________________________________________________________________ ELEMENTS\n\t/** @class mixin for Dataset collection elements.\n\t * When collection elements are passed from the API, the underlying element is\n\t * in a sub-object 'object' (IOW, a DCE representing an HDA will have HDA json in element.object).\n\t * This mixin uses the constructor and parse methods to merge that JSON with the DCE attribtues\n\t * effectively changing a DCE from a container to a subclass (has a --> is a).\n\t */\n\tvar DatasetCollectionElementMixin = {\n\t\n\t /** default attributes used by elements in a dataset collection */\n\t defaults : {\n\t model_class : 'DatasetCollectionElement',\n\t element_identifier : null,\n\t element_index : null,\n\t element_type : null\n\t },\n\t\n\t /** merge the attributes of the sub-object 'object' into this model */\n\t _mergeObject : function( attributes ){\n\t // if we don't preserve and correct ids here, the element id becomes the object id\n\t // and collision in backbone's _byId will occur and only\n\t _.extend( attributes, attributes.object, { element_id: attributes.id });\n\t delete attributes.object;\n\t return attributes;\n\t },\n\t\n\t /** override to merge this.object into this */\n\t constructor : function( attributes, options ){\n\t // console.debug( '\\t DatasetCollectionElement.constructor:', attributes, options );\n\t attributes = this._mergeObject( attributes );\n\t this.idAttribute = 'element_id';\n\t Backbone.Model.apply( this, arguments );\n\t },\n\t\n\t /** when the model is fetched, merge this.object into this */\n\t parse : function( response, options ){\n\t var attributes = response;\n\t attributes = this._mergeObject( attributes );\n\t return attributes;\n\t }\n\t};\n\t\n\t//TODO: unused?\n\t/** @class Concrete class of Generic DatasetCollectionElement */\n\tvar DatasetCollectionElement = Backbone.Model\n\t .extend( BASE_MVC.LoggableMixin )\n\t .extend( DatasetCollectionElementMixin )\n\t .extend({ _logNamespace : logNamespace });\n\t\n\t\n\t//==============================================================================\n\t/** @class Base/Abstract Backbone collection for Generic DCEs. */\n\tvar DCECollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend(\n\t/** @lends DCECollection.prototype */{\n\t _logNamespace : logNamespace,\n\t\n\t model: DatasetCollectionElement,\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t //logger : console,\n\t\n\t//TODO: unused?\n\t /** Set up.\n\t * @see Backbone.Collection#initialize\n\t */\n\t initialize : function( attributes, options ){\n\t this.debug( this + '(DCECollection).initialize:', attributes, options );\n\t options = options || {};\n\t //this._setUpListeners();\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'DatasetCollectionElementCollection(', this.length, ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone model for a dataset collection element that is a dataset (HDA).\n\t */\n\tvar DatasetDCE = DATASET_MODEL.DatasetAssociation.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,\n\t/** @lends DatasetDCE.prototype */{\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t //logger : console,\n\t\n\t /** url fn */\n\t url : function(){\n\t // won't always be an hda\n\t if( !this.has( 'history_id' ) ){\n\t console.warn( 'no endpoint for non-hdas within a collection yet' );\n\t // (a little silly since this api endpoint *also* points at hdas)\n\t return Galaxy.root + 'api/datasets';\n\t }\n\t return Galaxy.root + 'api/histories/' + this.get( 'history_id' ) + '/contents/' + this.get( 'id' );\n\t },\n\t\n\t defaults : _.extend( {},\n\t DATASET_MODEL.DatasetAssociation.prototype.defaults,\n\t DatasetCollectionElementMixin.defaults\n\t ),\n\t\n\t // because all objects have constructors (as this hashmap would even if this next line wasn't present)\n\t // the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model\n\t // - re-apply manually for now\n\t /** call the mixin constructor */\n\t constructor : function( attributes, options ){\n\t this.debug( '\\t DatasetDCE.constructor:', attributes, options );\n\t //DATASET_MODEL.DatasetAssociation.prototype.constructor.call( this, attributes, options );\n\t DatasetCollectionElementMixin.constructor.call( this, attributes, options );\n\t },\n\t\n\t//TODO: unused?\n\t /** set up */\n\t initialize : function( attributes, options ){\n\t this.debug( this + '(DatasetDCE).initialize:', attributes, options );\n\t DATASET_MODEL.DatasetAssociation.prototype.initialize.call( this, attributes, options );\n\t },\n\t\n\t /** Does this model already contain detailed data (as opposed to just summary level data)? */\n\t hasDetails : function(){\n\t // dataset collection api does return genome_build but doesn't return annotation\n\t return _.has( this.attributes, 'annotation' );\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t var objStr = this.get( 'element_identifier' );\n\t return ([ 'DatasetDCE(', objStr, ')' ].join( '' ));\n\t }\n\t}));\n\t\n\t\n\t//==============================================================================\n\t/** @class DCECollection of DatasetDCE's (a list of datasets, a pair of datasets).\n\t */\n\tvar DatasetDCECollection = DCECollection.extend(\n\t/** @lends DatasetDCECollection.prototype */{\n\t model: DatasetDCE,\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t //logger : console,\n\t\n\t//TODO: unused?\n\t /** set up */\n\t initialize : function( attributes, options ){\n\t this.debug( this + '(DatasetDCECollection).initialize:', attributes, options );\n\t DCECollection.prototype.initialize.call( this, attributes, options );\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'DatasetDCECollection(', this.length, ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//_________________________________________________________________________________________________ COLLECTIONS\n\t/** @class Backbone model for Dataset Collections.\n\t * The DC API returns an array of JSON objects under the attribute elements.\n\t * This model:\n\t * - removes that array/attribute ('elements') from the model,\n\t * - creates a bbone collection (of the class defined in the 'collectionClass' attribute),\n\t * - passes that json onto the bbone collection\n\t * - caches the bbone collection in this.elements\n\t */\n\tvar DatasetCollection = Backbone.Model\n\t .extend( BASE_MVC.LoggableMixin )\n\t .extend( BASE_MVC.SearchableModelMixin )\n\t .extend(/** @lends DatasetCollection.prototype */{\n\t _logNamespace : logNamespace,\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t //logger : console,\n\t\n\t /** default attributes for a model */\n\t defaults : {\n\t /* 'list', 'paired', or 'list:paired' */\n\t collection_type : null,\n\t //??\n\t deleted : false\n\t },\n\t\n\t /** Which class to use for elements */\n\t collectionClass : DCECollection,\n\t\n\t /** set up: create elements instance var and (on changes to elements) update them */\n\t initialize : function( model, options ){\n\t this.debug( this + '(DatasetCollection).initialize:', model, options, this );\n\t //historyContent.HistoryContent.prototype.initialize.call( this, attrs, options );\n\t this.elements = this._createElementsModel();\n\t this.on( 'change:elements', function(){\n\t this.log( 'change:elements' );\n\t//TODO: prob. better to update the collection instead of re-creating it\n\t this.elements = this._createElementsModel();\n\t });\n\t },\n\t\n\t /** move elements model attribute to full collection */\n\t _createElementsModel : function(){\n\t this.debug( this + '._createElementsModel', this.collectionClass, this.get( 'elements' ), this.elements );\n\t//TODO: same patterns as DatasetCollectionElement _createObjectModel - refactor to BASE_MVC.hasSubModel?\n\t var elements = this.get( 'elements' ) || [];\n\t this.unset( 'elements', { silent: true });\n\t this.elements = new this.collectionClass( elements );\n\t //this.debug( 'collectionClass:', this.collectionClass + '', this.elements );\n\t return this.elements;\n\t },\n\t\n\t // ........................................................................ common queries\n\t /** pass the elements back within the model json when this is serialized */\n\t toJSON : function(){\n\t var json = Backbone.Model.prototype.toJSON.call( this );\n\t if( this.elements ){\n\t json.elements = this.elements.toJSON();\n\t }\n\t return json;\n\t },\n\t\n\t /** Is this collection in a 'ready' state no processing (for the collection) is left\n\t * to do on the server.\n\t */\n\t inReadyState : function(){\n\t var populated = this.get( 'populated' );\n\t return ( this.isDeletedOrPurged() || populated );\n\t },\n\t\n\t //TODO:?? the following are the same interface as DatasetAssociation - can we combine?\n\t /** Does the DC contain any elements yet? Is a fetch() required? */\n\t hasDetails : function(){\n\t//TODO: this is incorrect for (accidentally) empty collections\n\t this.debug( 'hasDetails:', this.elements.length );\n\t return this.elements.length !== 0;\n\t },\n\t\n\t /** Given the filters, what models in this.elements would be returned? */\n\t getVisibleContents : function( filters ){\n\t // filters unused for now\n\t return this.elements;\n\t },\n\t\n\t // ........................................................................ ajax\n\t /** save this dataset, _Mark_ing it as deleted (just a flag) */\n\t 'delete' : function( options ){\n\t if( this.get( 'deleted' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: true }, options );\n\t },\n\t /** save this dataset, _Mark_ing it as undeleted */\n\t undelete : function( options ){\n\t if( !this.get( 'deleted' ) || this.get( 'purged' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: false }, options );\n\t },\n\t\n\t /** Is this collection deleted or purged? */\n\t isDeletedOrPurged : function(){\n\t return ( this.get( 'deleted' ) || this.get( 'purged' ) );\n\t },\n\t\n\t // ........................................................................ searchable\n\t /** searchable attributes for collections */\n\t searchAttributes : [\n\t 'name'\n\t ],\n\t\n\t // ........................................................................ misc\n\t /** String representation */\n\t toString : function(){\n\t var idAndName = [ this.get( 'id' ), this.get( 'name' ) || this.get( 'element_identifier' ) ];\n\t return 'DatasetCollection(' + ( idAndName.join(',') ) + ')';\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** Model for a DatasetCollection containing datasets (non-nested).\n\t */\n\tvar ListDatasetCollection = DatasetCollection.extend(\n\t/** @lends ListDatasetCollection.prototype */{\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t //logger : console,\n\t\n\t /** override since we know the collection will only contain datasets */\n\t collectionClass : DatasetDCECollection,\n\t\n\t//TODO: unused?\n\t initialize : function( attrs, options ){\n\t this.debug( this + '(ListDatasetCollection).initialize:', attrs, options );\n\t DatasetCollection.prototype.initialize.call( this, attrs, options );\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'ListDatasetCollection(', this.get( 'name' ), ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** Model for a DatasetCollection containing fwd/rev datasets (a list of 2).\n\t */\n\tvar PairDatasetCollection = ListDatasetCollection.extend(\n\t/** @lends PairDatasetCollection.prototype */{\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t //logger : console,\n\t\n\t//TODO: unused?\n\t /** */\n\t initialize : function( attrs, options ){\n\t this.debug( this + '(PairDatasetCollection).initialize:', attrs, options );\n\t ListDatasetCollection.prototype.initialize.call( this, attrs, options );\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'PairDatasetCollection(', this.get( 'name' ), ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//_________________________________________________________________________________________________ NESTED COLLECTIONS\n\t// this is where things get weird, man. Weird.\n\t//TODO: it might be possible to compact all the following...I think.\n\t//==============================================================================\n\t/** @class Backbone model for a Generic DatasetCollectionElement that is also a DatasetCollection\n\t * (a nested collection). Currently only list:paired.\n\t */\n\tvar NestedDCDCE = DatasetCollection.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,\n\t/** @lends NestedDCDCE.prototype */{\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t //logger : console,\n\t\n\t // because all objects have constructors (as this hashmap would even if this next line wasn't present)\n\t // the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model\n\t // - re-apply manually it now\n\t /** call the mixin constructor */\n\t constructor : function( attributes, options ){\n\t this.debug( '\\t NestedDCDCE.constructor:', attributes, options );\n\t DatasetCollectionElementMixin.constructor.call( this, attributes, options );\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );\n\t return ([ 'NestedDCDCE(', objStr, ')' ].join( '' ));\n\t }\n\t}));\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone collection containing Generic NestedDCDCE's (nested dataset collections).\n\t */\n\tvar NestedDCDCECollection = DCECollection.extend(\n\t/** @lends NestedDCDCECollection.prototype */{\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t //logger : console,\n\t\n\t /** This is a collection of nested collections */\n\t model: NestedDCDCE,\n\t\n\t//TODO: unused?\n\t /** set up */\n\t initialize : function( attrs, options ){\n\t this.debug( this + '(NestedDCDCECollection).initialize:', attrs, options );\n\t DCECollection.prototype.initialize.call( this, attrs, options );\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'NestedDCDCECollection(', this.length, ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone model for a paired dataset collection within a list:paired dataset collection.\n\t */\n\tvar NestedPairDCDCE = PairDatasetCollection.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,\n\t/** @lends NestedPairDCDCE.prototype */{\n\t//TODO:?? possibly rename to NestedDatasetCollection?\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t //logger : console,\n\t\n\t // because all objects have constructors (as this hashmap would even if this next line wasn't present)\n\t // the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model\n\t // - re-apply manually it now\n\t /** This is both a collection and a collection element - call the constructor */\n\t constructor : function( attributes, options ){\n\t this.debug( '\\t NestedPairDCDCE.constructor:', attributes, options );\n\t //DatasetCollection.constructor.call( this, attributes, options );\n\t DatasetCollectionElementMixin.constructor.call( this, attributes, options );\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );\n\t return ([ 'NestedPairDCDCE(', objStr, ')' ].join( '' ));\n\t }\n\t}));\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone collection for a backbone collection containing paired dataset collections.\n\t */\n\tvar NestedPairDCDCECollection = NestedDCDCECollection.extend(\n\t/** @lends PairDCDCECollection.prototype */{\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t //logger : console,\n\t\n\t /** We know this collection is composed of only nested pair collections */\n\t model: NestedPairDCDCE,\n\t\n\t//TODO: unused?\n\t /** set up */\n\t initialize : function( attrs, options ){\n\t this.debug( this + '(NestedPairDCDCECollection).initialize:', attrs, options );\n\t NestedDCDCECollection.prototype.initialize.call( this, attrs, options );\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'NestedPairDCDCECollection(', this.length, ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone Model for a DatasetCollection (list) that contains DatasetCollections (pairs).\n\t */\n\tvar ListPairedDatasetCollection = DatasetCollection.extend(\n\t/** @lends ListPairedDatasetCollection.prototype */{\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t //logger : console,\n\t\n\t /** list:paired is the only collection that itself contains collections */\n\t collectionClass : NestedPairDCDCECollection,\n\t\n\t//TODO: unused?\n\t /** set up */\n\t initialize : function( attributes, options ){\n\t this.debug( this + '(ListPairedDatasetCollection).initialize:', attributes, options );\n\t DatasetCollection.prototype.initialize.call( this, attributes, options );\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'ListPairedDatasetCollection(', this.get( 'name' ), ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t ListDatasetCollection : ListDatasetCollection,\n\t PairDatasetCollection : PairDatasetCollection,\n\t ListPairedDatasetCollection : ListPairedDatasetCollection\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)))\n\n/***/ },\n/* 31 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, $, jQuery) {\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(39),\n\t __webpack_require__(10),\n\t __webpack_require__(6),\n\t __webpack_require__(8),\n\t __webpack_require__(84),\n\t __webpack_require__(5),\n\t __webpack_require__(81)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( HDCA, STATES, BASE_MVC, UI_MODAL, naturalSort, _l ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'collections';\n\t/*==============================================================================\n\tTODO:\n\t use proper Element model and not just json\n\t straighten out createFn, collection.createHDCA\n\t possibly stop using modals for this\n\t It would be neat to do a drag and drop\n\t\n\t==============================================================================*/\n\t/** A view for both DatasetDCEs and NestedDCDCEs\n\t * (things that implement collection-model:DatasetCollectionElementMixin)\n\t */\n\tvar DatasetCollectionElementView = 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.selected = attributes.selected || false;\n\t },\n\t\n\t render : function(){\n\t this.$el\n\t .attr( 'data-element-id', this.element.id )\n\t .attr( 'draggable', true )\n\t .html( this.template({ element: this.element }) );\n\t if( this.selected ){\n\t this.$el.addClass( 'selected' );\n\t }\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 '',\n\t '<%- element.name %>',\n\t '',\n\t '',\n\t ].join('')),\n\t\n\t /** select this element and pub */\n\t select : function( toggle ){\n\t this.$el.toggleClass( 'selected', toggle );\n\t this.trigger( 'select', {\n\t source : this,\n\t selected : this.$el.hasClass( 'selected' )\n\t });\n\t },\n\t\n\t /** animate the removal of this element and pub */\n\t discard : function(){\n\t var view = this,\n\t parentWidth = this.$el.parent().width();\n\t this.$el.animate({ 'margin-right' : parentWidth }, 'fast', function(){\n\t view.trigger( 'discard', {\n\t source : view\n\t });\n\t view.destroy();\n\t });\n\t },\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 events : {\n\t 'click' : '_click',\n\t 'click .name' : '_clickName',\n\t 'click .discard': '_clickDiscard',\n\t\n\t 'dragstart' : '_dragstart',\n\t 'dragend' : '_dragend',\n\t 'dragover' : '_sendToParent',\n\t 'drop' : '_sendToParent'\n\t },\n\t\n\t /** select when the li is clicked */\n\t _click : function( ev ){\n\t ev.stopPropagation();\n\t this.select( ev );\n\t },\n\t\n\t /** rename a pair when the name is clicked */\n\t _clickName : function( ev ){\n\t ev.stopPropagation();\n\t ev.preventDefault();\n\t var promptString = [ _l( 'Enter a new name for the element' ), ':\\n(',\n\t _l( 'Note that changing the name here will not rename the dataset' ), ')' ].join( '' ),\n\t response = prompt( _l( 'Enter a new name for the element' ) + ':', this.element.name );\n\t if( response ){\n\t this.element.name = response;\n\t this.render();\n\t }\n\t //TODO: cancelling with ESC leads to closure of the creator...\n\t },\n\t\n\t /** discard when the discard button is clicked */\n\t _clickDiscard : function( ev ){\n\t ev.stopPropagation();\n\t this.discard();\n\t },\n\t\n\t /** dragging pairs for re-ordering */\n\t _dragstart : function( ev ){\n\t if( ev.originalEvent ){ ev = ev.originalEvent; }\n\t ev.dataTransfer.effectAllowed = 'move';\n\t ev.dataTransfer.setData( 'text/plain', JSON.stringify( this.element ) );\n\t\n\t this.$el.addClass( 'dragging' );\n\t this.$el.parent().trigger( 'collection-element.dragstart', [ this ] );\n\t },\n\t\n\t /** dragging for re-ordering */\n\t _dragend : function( ev ){\n\t this.$el.removeClass( 'dragging' );\n\t this.$el.parent().trigger( 'collection-element.dragend', [ this ] );\n\t },\n\t\n\t /** manually bubble up an event to the parent/container */\n\t _sendToParent : function( ev ){\n\t this.$el.parent().trigger( ev );\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\t/** An interface for building collections.\n\t */\n\tvar ListCollectionCreator = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n\t _logNamespace : logNamespace,\n\t\n\t /** the class used to display individual elements */\n\t elementViewClass : DatasetCollectionElementView,\n\t /** the class this creator will create and save */\n\t collectionClass : HDCA.HistoryListDatasetCollection,\n\t className : 'list-collection-creator collection-creator flex-row-container',\n\t\n\t /** minimum number of valid elements to start with in order to build a collection of this type */\n\t minElements : 1,\n\t\n\t defaultAttributes : {\n\t//TODO: remove - use new collectionClass().save()\n\t /** takes elements and creates the proper collection - returns a promise */\n\t creationFn : function(){ throw new TypeError( 'no creation fn for creator' ); },\n\t /** fn to call when the collection is created (scoped to this) */\n\t oncreate : function(){},\n\t /** fn to call when the cancel button is clicked (scoped to this) - if falsy, no btn is displayed */\n\t oncancel : function(){},\n\t /** distance from list edge to begin autoscrolling list */\n\t autoscrollDist : 24,\n\t /** Color passed to hoverhighlight */\n\t highlightClr : 'rgba( 64, 255, 255, 1.0 )'\n\t },\n\t\n\t /** set up initial options, instance vars, behaviors */\n\t initialize : function( attributes ){\n\t this.metric( 'ListCollectionCreator.initialize', attributes );\n\t var creator = this;\n\t _.each( this.defaultAttributes, function( value, key ){\n\t value = attributes[ key ] || value;\n\t creator[ key ] = value;\n\t });\n\t\n\t /** unordered, original list - cache to allow reversal */\n\t creator.initialElements = attributes.elements || [];\n\t\n\t this._instanceSetUp();\n\t this._elementsSetUp();\n\t this._setUpBehaviors();\n\t },\n\t\n\t /** set up instance vars */\n\t _instanceSetUp : function(){\n\t /** Ids of elements that have been selected by the user - to preserve over renders */\n\t this.selectedIds = {};\n\t /** DOM elements currently being dragged */\n\t this.$dragging = null;\n\t /** Used for blocking UI events during ajax/operations (don't post twice) */\n\t this.blocking = false;\n\t },\n\t\n\t // ------------------------------------------------------------------------ process raw list\n\t /** set up main data */\n\t _elementsSetUp : function(){\n\t //this.debug( '-- _dataSetUp' );\n\t /** a list of invalid elements and the reasons they aren't valid */\n\t this.invalidElements = [];\n\t//TODO: handle fundamental problem of syncing DOM, views, and list here\n\t /** data for list in progress */\n\t this.workingElements = [];\n\t /** views for workingElements */\n\t this.elementViews = [];\n\t\n\t // copy initial list, sort, add ids if needed\n\t this.workingElements = this.initialElements.slice( 0 );\n\t this._ensureElementIds();\n\t this._validateElements();\n\t this._mangleDuplicateNames();\n\t this._sortElements();\n\t },\n\t\n\t /** add ids to dataset objs in initial list if none */\n\t _ensureElementIds : function(){\n\t this.workingElements.forEach( function( element ){\n\t if( !element.hasOwnProperty( 'id' ) ){\n\t element.id = _.uniqueId();\n\t }\n\t });\n\t return this.workingElements;\n\t },\n\t\n\t /** separate working list into valid and invalid elements for this collection */\n\t _validateElements : function(){\n\t var creator = this,\n\t existingNames = {};\n\t creator.invalidElements = [];\n\t\n\t this.workingElements = this.workingElements.filter( function( element ){\n\t var problem = creator._isElementInvalid( element );\n\t if( problem ){\n\t creator.invalidElements.push({\n\t element : element,\n\t text : problem\n\t });\n\t }\n\t return !problem;\n\t });\n\t return this.workingElements;\n\t },\n\t\n\t /** describe what is wrong with a particular element if anything */\n\t _isElementInvalid : function( element ){\n\t if( element.history_content_type !== 'dataset' ){\n\t return _l( \"is not a dataset\" );\n\t }\n\t if( element.state !== STATES.OK ){\n\t if( _.contains( STATES.NOT_READY_STATES, element.state ) ){\n\t return _l( \"hasn't finished running yet\" );\n\t }\n\t return _l( \"has errored, is paused, or is not accessible\" );\n\t }\n\t if( element.deleted || element.purged ){\n\t return _l( \"has been deleted or purged\" );\n\t }\n\t return null;\n\t },\n\t\n\t /** mangle duplicate names using a mac-like '(counter)' addition to any duplicates */\n\t _mangleDuplicateNames : function(){\n\t var SAFETY = 900,\n\t counter = 1,\n\t existingNames = {};\n\t this.workingElements.forEach( function( element ){\n\t var currName = element.name;\n\t while( existingNames.hasOwnProperty( currName ) ){\n\t currName = element.name + ' (' + counter + ')';\n\t counter += 1;\n\t if( counter >= SAFETY ){\n\t throw new Error( 'Safety hit in while loop - thats impressive' );\n\t }\n\t }\n\t element.name = currName;\n\t existingNames[ element.name ] = true;\n\t });\n\t },\n\t\n\t /** sort a list of elements */\n\t _sortElements : function( list ){\n\t // // currently only natural sort by name\n\t // this.workingElements.sort( function( a, b ){ return naturalSort( a.name, b.name ); });\n\t // return this.workingElements;\n\t },\n\t\n\t // ------------------------------------------------------------------------ rendering\n\t // templates : ListCollectionCreator.templates,\n\t /** render the entire interface */\n\t render : function( speed, callback ){\n\t //this.debug( '-- _render' );\n\t if( this.workingElements.length < this.minElements ){\n\t return this._renderInvalid( speed, callback );\n\t }\n\t\n\t this.$el.empty().html( this.templates.main() );\n\t this._renderHeader( speed );\n\t this._renderMiddle( speed );\n\t this._renderFooter( speed );\n\t this._addPluginComponents();\n\t this.$( '.collection-name' ).focus();\n\t this.trigger( 'rendered', this );\n\t return this;\n\t },\n\t\n\t\n\t /** render a simplified interface aimed at telling the user why they can't move forward */\n\t _renderInvalid : function( speed, callback ){\n\t //this.debug( '-- _render' );\n\t this.$el.empty().html( this.templates.invalidInitial({\n\t problems: this.invalidElements,\n\t elements: this.workingElements,\n\t }));\n\t if( typeof this.oncancel === 'function' ){\n\t this.$( '.cancel-create.btn' ).show();\n\t }\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 var $header = this.$( '.header' ).empty().html( this.templates.header() )\n\t .find( '.help-content' ).prepend( $( this.templates.helpContent() ) );\n\t //TODO: should only show once despite calling _renderHeader again\n\t if( this.invalidElements.length ){\n\t this._invalidElementsAlert();\n\t }\n\t return $header;\n\t },\n\t\n\t /** render the middle including the elements */\n\t _renderMiddle : function( speed, callback ){\n\t var $middle = this.$( '.middle' ).empty().html( this.templates.middle() );\n\t this._renderList( speed );\n\t return $middle;\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( this.templates.footer() );\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.$( '.help-content i' ).hoverhighlight( '.collection-creator', this.highlightClr );\n\t },\n\t\n\t /** build and show an alert describing any elements that could not be included due to problems */\n\t _invalidElementsAlert : function(){\n\t this._showAlert( this.templates.invalidElements({ problems: this.invalidElements }), 'alert-warning' );\n\t },\n\t\n\t /** add (or clear if clear is truthy) a validation warning to the DOM element described in what */\n\t _validationWarning : function( what, clear ){\n\t var VALIDATION_CLASS = 'validation-warning';\n\t if( what === 'name' ){\n\t what = this.$( '.collection-name' ).add( this.$( '.collection-name-prompt' ) );\n\t this.$( '.collection-name' ).focus().select();\n\t }\n\t if( clear ){\n\t what = what || this.$( '.' + VALIDATION_CLASS );\n\t what.removeClass( VALIDATION_CLASS );\n\t } else {\n\t what.addClass( VALIDATION_CLASS );\n\t }\n\t },\n\t\n\t _disableNameAndCreate : function( disable ){\n\t disable = !_.isUndefined( disable )? disable : true;\n\t if( disable ){\n\t this.$( '.collection-name' ).prop( 'disabled', true );\n\t this.$( '.create-collection' ).toggleClass( 'disabled', true );\n\t // } else {\n\t // this.$( '.collection-name' ).prop( 'disabled', false );\n\t // this.$( '.create-collection' ).removeClass( 'disable' );\n\t }\n\t },\n\t\n\t // ------------------------------------------------------------------------ rendering elements\n\t /** conv. to the main list display DOM */\n\t $list : function(){\n\t return this.$( '.collection-elements' );\n\t },\n\t\n\t /** show or hide the clear selected control based on the num of selected elements */\n\t _renderClearSelected : function(){\n\t if( _.size( this.selectedIds ) ){\n\t this.$( '.collection-elements-controls > .clear-selected' ).show();\n\t } else {\n\t this.$( '.collection-elements-controls > .clear-selected' ).hide();\n\t }\n\t },\n\t\n\t /** render the elements in order (or a warning if no elements found) */\n\t _renderList : function( speed, callback ){\n\t //this.debug( '-- _renderList' );\n\t var creator = this,\n\t $tmp = jQuery( '' ),\n\t $list = creator.$list();\n\t\n\t _.each( this.elementViews, function( view ){\n\t view.destroy();\n\t creator.removeElementView( view );\n\t });\n\t\n\t // if( !this.workingElements.length ){\n\t // this._renderNoValidElements();\n\t // return;\n\t // }\n\t\n\t creator.workingElements.forEach( function( element ){\n\t var elementView = creator._createElementView( element );\n\t $tmp.append( elementView.$el );\n\t });\n\t\n\t creator._renderClearSelected();\n\t $list.empty().append( $tmp.children() );\n\t _.invoke( creator.elementViews, 'render' );\n\t\n\t if( $list.height() > $list.css( 'max-height' ) ){\n\t $list.css( 'border-width', '1px 0px 1px 0px' );\n\t } else {\n\t $list.css( 'border-width', '0px' );\n\t }\n\t },\n\t\n\t /** create an element view, cache in elementViews, set up listeners, and return */\n\t _createElementView : function( element ){\n\t var elementView = new this.elementViewClass({\n\t//TODO: use non-generic class or not all\n\t // model : COLLECTION.DatasetDCE( element )\n\t element : element,\n\t selected: _.has( this.selectedIds, element.id )\n\t });\n\t this.elementViews.push( elementView );\n\t this._listenToElementView( elementView );\n\t return elementView;\n\t },\n\t\n\t /** listen to any element events */\n\t _listenToElementView : function( view ){\n\t var creator = this;\n\t creator.listenTo( view, {\n\t select : function( data ){\n\t var element = data.source.element;\n\t if( data.selected ){\n\t creator.selectedIds[ element.id ] = true;\n\t } else {\n\t delete creator.selectedIds[ element.id ];\n\t }\n\t creator.trigger( 'elements:select', data );\n\t },\n\t discard : function( data ){\n\t creator.trigger( 'elements:discard', data );\n\t }\n\t });\n\t },\n\t\n\t /** add a new element view based on the json in element */\n\t addElementView : function( element ){\n\t//TODO: workingElements is sorted, add element in appropo index\n\t // add element, sort elements, find element index\n\t // var view = this._createElementView( element );\n\t // return view;\n\t },\n\t\n\t /** stop listening to view and remove from caches */\n\t removeElementView : function( view ){\n\t delete this.selectedIds[ view.element.id ];\n\t this._renderClearSelected();\n\t\n\t this.elementViews = _.without( this.elementViews, view );\n\t this.stopListening( view );\n\t },\n\t\n\t /** render a message in the list that no elements remain to create a collection */\n\t _renderNoElementsLeft : function(){\n\t this._disableNameAndCreate( true );\n\t this.$( '.collection-elements' ).append( this.templates.noElementsLeft() );\n\t },\n\t\n\t // /** render a message in the list that no valid elements were found to create a collection */\n\t // _renderNoValidElements : function(){\n\t // this._disableNameAndCreate( true );\n\t // this.$( '.collection-elements' ).append( this.templates.noValidElements() );\n\t // },\n\t\n\t // ------------------------------------------------------------------------ API\n\t /** convert element into JSON compatible with the collections API */\n\t _elementToJSON : function( element ){\n\t // return element.toJSON();\n\t return element;\n\t },\n\t\n\t /** create the collection via the API\n\t * @returns {jQuery.xhr Object} the jquery ajax request\n\t */\n\t createList : function( name ){\n\t if( !this.workingElements.length ){\n\t var message = _l( 'No valid elements for final list' ) + '. ';\n\t message += '' + _l( 'Cancel' ) + ' ';\n\t message += _l( 'or' );\n\t message += ' ' + _l( 'start over' ) + '.';\n\t this._showAlert( message );\n\t return;\n\t }\n\t\n\t var creator = this,\n\t elements = this.workingElements.map( function( element ){\n\t return creator._elementToJSON( element );\n\t });\n\t\n\t creator.blocking = true;\n\t return creator.creationFn( elements, name )\n\t .always( function(){\n\t creator.blocking = false;\n\t })\n\t .fail( function( xhr, status, message ){\n\t creator.trigger( 'error', {\n\t xhr : xhr,\n\t status : status,\n\t message : _l( 'An error occurred while creating this collection' )\n\t });\n\t })\n\t .done( function( response, message, xhr ){\n\t creator.trigger( 'collection:created', response, message, xhr );\n\t creator.metric( 'collection:created', response );\n\t if( typeof creator.oncreate === 'function' ){\n\t creator.oncreate.call( this, response, message, xhr );\n\t }\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ events\n\t /** set up event handlers on self */\n\t _setUpBehaviors : function(){\n\t this.on( 'error', this._errorHandler );\n\t\n\t this.once( 'rendered', function(){\n\t this.trigger( 'rendered:initial', this );\n\t });\n\t\n\t this.on( 'elements:select', function( data ){\n\t this._renderClearSelected();\n\t });\n\t\n\t this.on( 'elements:discard', function( data ){\n\t var element = data.source.element;\n\t this.removeElementView( data.source );\n\t\n\t this.workingElements = _.without( this.workingElements, element );\n\t if( !this.workingElements.length ){\n\t this._renderNoElementsLeft();\n\t }\n\t });\n\t\n\t //this.on( 'all', function(){\n\t // this.info( arguments );\n\t //});\n\t return this;\n\t },\n\t\n\t /** handle errors with feedback and details to the user (if available) */\n\t _errorHandler : function( data ){\n\t this.error( data );\n\t\n\t var creator = this;\n\t content = data.message || _l( 'An error occurred' );\n\t if( data.xhr ){\n\t var xhr = data.xhr,\n\t message = data.message;\n\t if( xhr.readyState === 0 && xhr.status === 0 ){\n\t content += ': ' + _l( 'Galaxy could not be reached and may be updating.' ) +\n\t _l( ' Try again in a few minutes.' );\n\t } else if( xhr.responseJSON ){\n\t content += ':
' + JSON.stringify( xhr.responseJSON ) + '
';\n\t } else {\n\t content += ': ' + message;\n\t }\n\t }\n\t creator._showAlert( content, 'alert-danger' );\n\t },\n\t\n\t events : {\n\t // header\n\t 'click .more-help' : '_clickMoreHelp',\n\t 'click .less-help' : '_clickLessHelp',\n\t 'click .main-help' : '_toggleHelp',\n\t 'click .header .alert button' : '_hideAlert',\n\t\n\t 'click .reset' : 'reset',\n\t 'click .clear-selected' : 'clearSelectedElements',\n\t\n\t // elements - selection\n\t 'click .collection-elements' : 'clearSelectedElements',\n\t\n\t // elements - drop target\n\t // 'dragenter .collection-elements': '_dragenterElements',\n\t // 'dragleave .collection-elements': '_dragleaveElements',\n\t 'dragover .collection-elements' : '_dragoverElements',\n\t 'drop .collection-elements' : '_dropElements',\n\t\n\t // these bubble up from the elements as custom events\n\t 'collection-element.dragstart .collection-elements' : '_elementDragstart',\n\t 'collection-element.dragend .collection-elements' : '_elementDragend',\n\t\n\t // footer\n\t 'change .collection-name' : '_changeName',\n\t 'keydown .collection-name' : '_nameCheckForEnter',\n\t 'click .cancel-create' : function( ev ){\n\t if( typeof this.oncancel === 'function' ){\n\t this.oncancel.call( this );\n\t }\n\t },\n\t 'click .create-collection' : '_clickCreate'//,\n\t },\n\t\n\t // ........................................................................ header\n\t /** expand help */\n\t _clickMoreHelp : function( ev ){\n\t ev.stopPropagation();\n\t this.$( '.main-help' ).addClass( 'expanded' );\n\t this.$( '.more-help' ).hide();\n\t },\n\t /** collapse help */\n\t _clickLessHelp : function( ev ){\n\t ev.stopPropagation();\n\t this.$( '.main-help' ).removeClass( 'expanded' );\n\t this.$( '.more-help' ).show();\n\t },\n\t /** toggle help */\n\t _toggleHelp : function( ev ){\n\t ev.stopPropagation();\n\t this.$( '.main-help' ).toggleClass( 'expanded' );\n\t this.$( '.more-help' ).toggle();\n\t },\n\t\n\t /** show an alert on the top of the interface containing message (alertClass is bootstrap's alert-*) */\n\t _showAlert : function( message, alertClass ){\n\t alertClass = alertClass || 'alert-danger';\n\t this.$( '.main-help' ).hide();\n\t this.$( '.header .alert' )\n\t .attr( 'class', 'alert alert-dismissable' ).addClass( alertClass ).show()\n\t .find( '.alert-message' ).html( message );\n\t },\n\t /** hide the alerts at the top */\n\t _hideAlert : function( message ){\n\t this.$( '.main-help' ).show();\n\t this.$( '.header .alert' ).hide();\n\t },\n\t\n\t // ........................................................................ elements\n\t /** reset all data to the initial state */\n\t reset : function(){\n\t this._instanceSetUp();\n\t this._elementsSetUp();\n\t this.render();\n\t },\n\t\n\t /** deselect all elements */\n\t clearSelectedElements : function( ev ){\n\t this.$( '.collection-elements .collection-element' ).removeClass( 'selected' );\n\t this.$( '.collection-elements-controls > .clear-selected' ).hide();\n\t },\n\t\n\t //_dragenterElements : function( ev ){\n\t // //this.debug( '_dragenterElements:', ev );\n\t //},\n\t//TODO: if selected are dragged out of the list area - remove the placeholder - cuz it won't work anyway\n\t // _dragleaveElements : function( ev ){\n\t // //this.debug( '_dragleaveElements:', ev );\n\t // },\n\t\n\t /** track the mouse drag over the list adding a placeholder to show where the drop would occur */\n\t _dragoverElements : function( ev ){\n\t //this.debug( '_dragoverElements:', ev );\n\t ev.preventDefault();\n\t\n\t var $list = this.$list();\n\t this._checkForAutoscroll( $list, ev.originalEvent.clientY );\n\t var $nearest = this._getNearestElement( ev.originalEvent.clientY );\n\t\n\t //TODO: no need to re-create - move instead\n\t this.$( '.element-drop-placeholder' ).remove();\n\t var $placeholder = $( '' );\n\t if( !$nearest.length ){\n\t $list.append( $placeholder );\n\t } else {\n\t $nearest.before( $placeholder );\n\t }\n\t },\n\t\n\t /** If the mouse is near enough to the list's top or bottom, scroll the list */\n\t _checkForAutoscroll : function( $element, y ){\n\t var AUTOSCROLL_SPEED = 2,\n\t offset = $element.offset(),\n\t scrollTop = $element.scrollTop(),\n\t upperDist = y - offset.top,\n\t lowerDist = ( offset.top + $element.outerHeight() ) - y;\n\t if( upperDist >= 0 && upperDist < this.autoscrollDist ){\n\t $element.scrollTop( scrollTop - AUTOSCROLL_SPEED );\n\t } else if( lowerDist >= 0 && lowerDist < this.autoscrollDist ){\n\t $element.scrollTop( scrollTop + AUTOSCROLL_SPEED );\n\t }\n\t },\n\t\n\t /** get the nearest element based on the mouse's Y coordinate.\n\t * If the y is at the end of the list, return an empty jQuery object.\n\t */\n\t _getNearestElement : function( y ){\n\t var WIGGLE = 4,\n\t lis = this.$( '.collection-elements li.collection-element' ).toArray();\n\t for( var i=0; i y && top - halfHeight < y ){\n\t return $li;\n\t }\n\t }\n\t return $();\n\t },\n\t\n\t /** drop (dragged/selected elements) onto the list, re-ordering the internal list */\n\t _dropElements : function( ev ){\n\t if( ev.originalEvent ){ ev = ev.originalEvent; }\n\t // both required for firefox\n\t ev.preventDefault();\n\t ev.dataTransfer.dropEffect = 'move';\n\t\n\t // insert before the nearest element or after the last.\n\t var $nearest = this._getNearestElement( ev.clientY );\n\t if( $nearest.length ){\n\t this.$dragging.insertBefore( $nearest );\n\t } else {\n\t // no nearest before - insert after last element\n\t this.$dragging.insertAfter( this.$( '.collection-elements .collection-element' ).last() );\n\t }\n\t // resync the creator's list based on the new DOM order\n\t this._syncOrderToDom();\n\t return false;\n\t },\n\t\n\t /** resync the creator's list of elements based on the DOM order */\n\t _syncOrderToDom : function(){\n\t var creator = this,\n\t newElements = [];\n\t //TODO: doesn't seem wise to use the dom to store these - can't we sync another way?\n\t this.$( '.collection-elements .collection-element' ).each( function(){\n\t var id = $( this ).attr( 'data-element-id' ),\n\t element = _.findWhere( creator.workingElements, { id: id });\n\t if( element ){\n\t newElements.push( element );\n\t } else {\n\t console.error( 'missing element: ', id );\n\t }\n\t });\n\t this.workingElements = newElements;\n\t this._renderList();\n\t },\n\t\n\t /** drag communication with element sub-views: dragstart */\n\t _elementDragstart : function( ev, element ){\n\t // auto select the element causing the event and move all selected\n\t element.select( true );\n\t this.$dragging = this.$( '.collection-elements .collection-element.selected' );\n\t },\n\t\n\t /** drag communication with element sub-views: dragend - remove the placeholder */\n\t _elementDragend : function( ev, element ){\n\t $( '.element-drop-placeholder' ).remove();\n\t this.$dragging = null;\n\t },\n\t\n\t // ........................................................................ footer\n\t /** handle a collection name change */\n\t _changeName : function( ev ){\n\t this._validationWarning( 'name', !!this._getName() );\n\t },\n\t\n\t /** check for enter key press when in the collection name and submit */\n\t _nameCheckForEnter : function( ev ){\n\t if( ev.keyCode === 13 && !this.blocking ){\n\t this._clickCreate();\n\t }\n\t },\n\t\n\t /** get the current collection name */\n\t _getName : function(){\n\t return _.escape( this.$( '.collection-name' ).val() );\n\t },\n\t\n\t /** attempt to create the current collection */\n\t _clickCreate : function( ev ){\n\t var name = this._getName();\n\t if( !name ){\n\t this._validationWarning( 'name' );\n\t } else if( !this.blocking ){\n\t this.createList( name );\n\t }\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 : {\n\t /** the skeleton */\n\t main : _.template([\n\t '',\n\t '',\n\t ''\n\t ].join('')),\n\t\n\t /** the header (not including help text) */\n\t header : _.template([\n\t '
'\n\t ].join('')),\n\t\n\t /** help content */\n\t helpContent : _.template([\n\t '
', _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 '
',\n\t '
', _l([\n\t 'Rename elements in the list by clicking on ',\n\t 'the existing name.'\n\t ].join( '' )), '
',\n\t '
', _l([\n\t 'Discard elements from the final created list by clicking on the ',\n\t '\"Discard\" button.'\n\t ].join( '' )), '
',\n\t '
', _l([\n\t 'Reorder the list by clicking and dragging elements. Select multiple elements by clicking on ',\n\t 'them and you can then move those selected by dragging the ',\n\t 'entire group. Deselect them by clicking them again or by clicking the ',\n\t 'the \"Clear selected\" link.'\n\t ].join( '' )), '
',\n\t '
', _l([\n\t 'Click the \"Start over\" link to begin again as if you had just opened ',\n\t 'the interface.'\n\t ].join( '' )), '
',\n\t '
', _l([\n\t 'Click the \"Cancel\" button to exit the interface.'\n\t ].join( '' )), '
',\n\t '
',\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 '
<% _.each( problems, function( problem ){ %>',\n\t '
<%- problem.element.name %>: <%- problem.text %>
',\n\t '<% }); %>
'\n\t ].join('')),\n\t\n\t /** shown in list when all elements are discarded */\n\t noElementsLeft : _.template([\n\t '
',\n\t _l( 'No elements left! ' ),\n\t _l( 'Would you like to ' ), '', _l( 'start over' ), '?',\n\t '
'\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 '',\n\t '<% if( _.size( problems ) ){ %>',\n\t _l( 'The following selections could not be included due to problems' ), ':',\n\t '
<% _.each( problems, function( problem ){ %>',\n\t '
<%- problem.element.name %>: <%- problem.text %>
',\n\t '<% }); %>
',\n\t '<% } else if( _.size( elements ) < 1 ){ %>',\n\t _l( 'No datasets were selected' ), '.',\n\t '<% } %>',\n\t ' ',\n\t _l( 'At least one element is needed for the collection' ), '. ',\n\t _l( 'You may need to ' ),\n\t '', _l( 'cancel' ), ' ',\n\t _l( 'and reselect new elements' ), '.',\n\t '',\n\t '
',\n\t '
',\n\t ''\n\t ].join('')),\n\t },\n\t\n\t // ------------------------------------------------------------------------ misc\n\t /** string rep */\n\t toString : function(){ return 'ListCollectionCreator'; }\n\t});\n\t\n\t\n\t\n\t//=============================================================================\n\t/** Create a modal and load its body with the given CreatorClass creator type\n\t * @returns {Deferred} resolved when creator has built a collection.\n\t */\n\tvar collectionCreatorModal = function _collectionCreatorModal( elements, options, CreatorClass ){\n\t\n\t var deferred = jQuery.Deferred(),\n\t modal = Galaxy.modal || ( new UI_MODAL.View() ),\n\t creator;\n\t\n\t options = _.defaults( options || {}, {\n\t elements : elements,\n\t oncancel : function(){\n\t modal.hide();\n\t deferred.reject( 'cancelled' );\n\t },\n\t oncreate : function( creator, response ){\n\t modal.hide();\n\t deferred.resolve( response );\n\t }\n\t });\n\t\n\t creator = new CreatorClass( options );\n\t modal.show({\n\t title : options.title || _l( 'Create a collection' ),\n\t body : creator.$el,\n\t width : '80%',\n\t height : '100%',\n\t closing_events: true\n\t });\n\t creator.render();\n\t window._collectionCreator = creator;\n\t\n\t //TODO: remove modal header\n\t return deferred;\n\t};\n\t\n\t/** List collection flavor of collectionCreatorModal. */\n\tvar listCollectionCreatorModal = function _listCollectionCreatorModal( elements, options ){\n\t options = options || {};\n\t options.title = _l( 'Create a collection from a list of datasets' );\n\t return collectionCreatorModal( elements, options, ListCollectionCreator );\n\t};\n\t\n\t\n\t//==============================================================================\n\t/** Use a modal to create a list collection, then add it to the given history contents.\n\t * @returns {Deferred} resolved when the collection is added to the history.\n\t */\n\tfunction createListCollection( contents ){\n\t var elements = contents.toJSON(),\n\t promise = listCollectionCreatorModal( elements, {\n\t creationFn : function( elements, name ){\n\t elements = elements.map( function( element ){\n\t return {\n\t id : element.id,\n\t name : element.name,\n\t //TODO: this allows for list:list even if the filter above does not - reconcile\n\t src : ( element.history_content_type === 'dataset'? 'hda' : 'hdca' )\n\t };\n\t });\n\t return contents.createHDCA( elements, 'list', name );\n\t }\n\t });\n\t return promise;\n\t}\n\t\n\t//==============================================================================\n\t return {\n\t DatasetCollectionElementView: DatasetCollectionElementView,\n\t ListCollectionCreator : ListCollectionCreator,\n\t\n\t collectionCreatorModal : collectionCreatorModal,\n\t listCollectionCreatorModal : listCollectionCreatorModal,\n\t createListCollection : createListCollection\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1), __webpack_require__(1)))\n\n/***/ },\n/* 32 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(jQuery, Backbone, $, _) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(42),\n\t __webpack_require__(10),\n\t __webpack_require__(23),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( LIST_ITEM, STATES, faIconButton, BASE_MVC, _l ){\n\t'use strict';\n\t\n\tvar logNamespace = 'dataset';\n\t/*==============================================================================\n\tTODO:\n\t straighten out state rendering and templates used\n\t inaccessible/STATES.NOT_VIEWABLE is a special case\n\t simplify button rendering\n\t\n\t==============================================================================*/\n\tvar _super = LIST_ITEM.ListItemView;\n\t/** @class Read only list view for either LDDAs, HDAs, or HDADCEs.\n\t * Roughly, any DatasetInstance (and not a raw Dataset).\n\t */\n\tvar DatasetListItemView = _super.extend(\n\t/** @lends DatasetListItemView.prototype */{\n\t _logNamespace : logNamespace,\n\t\n\t className : _super.prototype.className + \" dataset\",\n\t //TODO:?? doesn't exactly match an hda's type_id\n\t id : function(){\n\t return [ 'dataset', this.model.get( 'id' ) ].join( '-' );\n\t },\n\t\n\t /** Set up: instance vars, options, and event handlers */\n\t initialize : function( attributes ){\n\t if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; }\n\t this.log( this + '.initialize:', attributes );\n\t _super.prototype.initialize.call( this, attributes );\n\t\n\t /** where should pages from links be displayed? (default to new tab/window) */\n\t this.linkTarget = attributes.linkTarget || '_blank';\n\t },\n\t\n\t /** event listeners */\n\t _setUpListeners : function(){\n\t _super.prototype._setUpListeners.call( this );\n\t\n\t // re-rendering on any model changes\n\t this.listenTo( this.model, 'change', function( model, options ){\n\t // if the model moved into the ready state and is expanded without details, fetch those details now\n\t if( this.model.changedAttributes().state && this.model.inReadyState()\n\t && this.expanded && !this.model.hasDetails() ){\n\t // will render automatically (due to fetch -> change)\n\t this.model.fetch();\n\t\n\t } else {\n\t this.render();\n\t }\n\t });\n\t },\n\t\n\t // ......................................................................... expandable\n\t /** In this override, only get details if in the ready state, get rerunnable if in other states.\n\t * Note: fetch with no 'change' event triggering to prevent automatic rendering.\n\t */\n\t _fetchModelDetails : function(){\n\t var view = this;\n\t if( view.model.inReadyState() && !view.model.hasDetails() ){\n\t return view.model.fetch({ silent: true });\n\t }\n\t return jQuery.when();\n\t },\n\t\n\t // ......................................................................... removal\n\t /** Remove this view's html from the DOM and remove all event listeners.\n\t * @param {Number or String} speed jq effect speed\n\t * @param {Function} callback an optional function called when removal is done (scoped to this view)\n\t */\n\t remove : function( speed, callback ){\n\t var view = this;\n\t speed = speed || this.fxSpeed;\n\t this.$el.fadeOut( speed, function(){\n\t Backbone.View.prototype.remove.call( view );\n\t if( callback ){ callback.call( view ); }\n\t });\n\t },\n\t\n\t // ......................................................................... rendering\n\t /* TODO:\n\t dataset states are the issue primarily making dataset rendering complex\n\t each state should have it's own way of displaying/set of details\n\t often with different actions that can be applied\n\t throw in deleted/purged/visible and things get complicated easily\n\t I've considered (a couple of times) - creating a view for each state\n\t - but recreating the view during an update...seems wrong\n\t */\n\t /** Render this HDA, set up ui.\n\t * @param {Number or String} speed jq fx speed\n\t * @returns {Object} this\n\t */\n\t render : function( speed ){\n\t //HACK: hover exit doesn't seem to be called on prev. tooltips when RE-rendering - so: no tooltip hide\n\t // handle that here by removing previous view's tooltips\n\t //this.$el.find(\"[title]\").tooltip( \"destroy\" );\n\t return _super.prototype.render.call( this, speed );\n\t },\n\t\n\t /** In this override, add the dataset state as a class for use with state-based CSS */\n\t _swapNewRender : function( $newRender ){\n\t _super.prototype._swapNewRender.call( this, $newRender );\n\t if( this.model.has( 'state' ) ){\n\t this.$el.addClass( 'state-' + this.model.get( 'state' ) );\n\t }\n\t return this.$el;\n\t },\n\t\n\t // ................................................................................ titlebar\n\t /** In this override, add the dataset display button. */\n\t _renderPrimaryActions : function(){\n\t // render just the display for read-only\n\t return [ this._renderDisplayButton() ];\n\t },\n\t\n\t /** Render icon-button to display dataset data */\n\t _renderDisplayButton : function(){\n\t//TODO:?? too complex - possibly move into template\n\t // don't show display if not viewable or not accessible\n\t var state = this.model.get( 'state' );\n\t if( ( state === STATES.NOT_VIEWABLE )\n\t || ( state === STATES.DISCARDED )\n\t || ( !this.model.get( 'accessible' ) ) ){\n\t return null;\n\t }\n\t\n\t var displayBtnData = {\n\t target : this.linkTarget,\n\t classes : 'display-btn'\n\t };\n\t\n\t // show a disabled display if the data's been purged\n\t if( this.model.get( 'purged' ) ){\n\t displayBtnData.disabled = true;\n\t displayBtnData.title = _l( 'Cannot display datasets removed from disk' );\n\t\n\t // disable if still uploading\n\t } else if( state === STATES.UPLOAD ){\n\t displayBtnData.disabled = true;\n\t displayBtnData.title = _l( 'This dataset must finish uploading before it can be viewed' );\n\t\n\t // disable if still new\n\t } else if( state === STATES.NEW ){\n\t displayBtnData.disabled = true;\n\t displayBtnData.title = _l( 'This dataset is not yet viewable' );\n\t\n\t } else {\n\t displayBtnData.title = _l( 'View data' );\n\t\n\t // default link for dataset\n\t displayBtnData.href = this.model.urls.display;\n\t\n\t // add frame manager option onclick event\n\t var self = this;\n\t displayBtnData.onclick = function( ev ){\n\t if (Galaxy.frame && Galaxy.frame.active) {\n\t // Add dataset to frames.\n\t Galaxy.frame.addDataset(self.model.get('id'));\n\t ev.preventDefault();\n\t }\n\t };\n\t }\n\t displayBtnData.faIcon = 'fa-eye';\n\t return faIconButton( displayBtnData );\n\t },\n\t\n\t // ......................................................................... rendering details\n\t /** Render the enclosing div of the hda body and, if expanded, the html in the body\n\t * @returns {jQuery} rendered DOM\n\t */\n\t _renderDetails : function(){\n\t //TODO: generalize to be allow different details for each state\n\t\n\t // no access - render nothing but a message\n\t if( this.model.get( 'state' ) === STATES.NOT_VIEWABLE ){\n\t return $( this.templates.noAccess( this.model.toJSON(), this ) );\n\t }\n\t\n\t var $details = _super.prototype._renderDetails.call( this );\n\t $details.find( '.actions .left' ).empty().append( this._renderSecondaryActions() );\n\t $details.find( '.summary' ).html( this._renderSummary() )\n\t .prepend( this._renderDetailMessages() );\n\t $details.find( '.display-applications' ).html( this._renderDisplayApplications() );\n\t\n\t//TODO: double tap\n\t this._setUpBehaviors( $details );\n\t return $details;\n\t },\n\t\n\t /** Defer to the appropo summary rendering fn based on state */\n\t _renderSummary : function(){\n\t var json = this.model.toJSON(),\n\t summaryRenderFn = this.templates.summaries[ json.state ];\n\t summaryRenderFn = summaryRenderFn || this.templates.summaries.unknown;\n\t return summaryRenderFn( json, this );\n\t },\n\t\n\t /** Render messages to be displayed only when the details are shown */\n\t _renderDetailMessages : function(){\n\t var view = this,\n\t $warnings = $( '' ),\n\t json = view.model.toJSON();\n\t//TODO:! unordered (map)\n\t _.each( view.templates.detailMessages, function( templateFn ){\n\t $warnings.append( $( templateFn( json, view ) ) );\n\t });\n\t return $warnings;\n\t },\n\t\n\t /** Render the external display application links */\n\t _renderDisplayApplications : function(){\n\t if( this.model.isDeletedOrPurged() ){ return ''; }\n\t // render both old and new display apps using the same template\n\t return [\n\t this.templates.displayApplications( this.model.get( 'display_apps' ), this ),\n\t this.templates.displayApplications( this.model.get( 'display_types' ), this )\n\t ].join( '' );\n\t },\n\t\n\t // ......................................................................... secondary/details actions\n\t /** A series of links/buttons for less commonly used actions: re-run, info, etc. */\n\t _renderSecondaryActions : function(){\n\t this.debug( '_renderSecondaryActions' );\n\t switch( this.model.get( 'state' ) ){\n\t case STATES.NOT_VIEWABLE:\n\t return [];\n\t case STATES.OK:\n\t case STATES.FAILED_METADATA:\n\t case STATES.ERROR:\n\t return [ this._renderDownloadButton(), this._renderShowParamsButton() ];\n\t }\n\t return [ this._renderShowParamsButton() ];\n\t },\n\t\n\t /** Render icon-button to show the input and output (stdout/err) for the job that created this.\n\t * @returns {jQuery} rendered DOM\n\t */\n\t _renderShowParamsButton : function(){\n\t // gen. safe to show in all cases\n\t return faIconButton({\n\t title : _l( 'View details' ),\n\t classes : 'params-btn',\n\t href : this.model.urls.show_params,\n\t target : this.linkTarget,\n\t faIcon : 'fa-info-circle'\n\t });\n\t },\n\t\n\t /** Render icon-button/popupmenu to download the data (and/or the associated meta files (bai, etc.)) for this.\n\t * @returns {jQuery} rendered DOM\n\t */\n\t _renderDownloadButton : function(){\n\t//TODO: to (its own) template fn\n\t // don't show anything if the data's been purged\n\t if( this.model.get( 'purged' ) || !this.model.hasData() ){ return null; }\n\t\n\t // return either: a popupmenu with links to download assoc. meta files (if there are meta files)\n\t // or a single download icon-button (if there are no meta files)\n\t if( !_.isEmpty( this.model.get( 'meta_files' ) ) ){\n\t return this._renderMetaFileDownloadButton();\n\t }\n\t\n\t return $([\n\t '',\n\t '',\n\t ''\n\t ].join( '' ));\n\t },\n\t\n\t /** Render the download button which opens a dropdown with links to download assoc. meta files (indeces, etc.) */\n\t _renderMetaFileDownloadButton : function(){\n\t var urls = this.model.urls;\n\t return $([\n\t '
'\n\t ], 'dataset' );\n\t\n\t // messages to be displayed only within the details section ('below the fold')\n\t var detailMessageTemplates = {\n\t resubmitted : BASE_MVC.wrapTemplate([\n\t // deleted not purged\n\t '<% if( model.resubmitted ){ %>',\n\t '
',\n\t _l( 'The job creating this dataset has been resubmitted' ),\n\t '
',\n\t '<% } %>'\n\t ])\n\t };\n\t\n\t // this is applied to both old and new style display apps\n\t var displayApplicationsTemplate = BASE_MVC.wrapTemplate([\n\t '<% _.each( apps, function( app ){ %>',\n\t '
',\n\t // space for title bar buttons - gen. floated to the right\n\t '',\n\t '',\n\t\n\t // expandable area for more details\n\t '',\n\t '
'\n\t ]);\n\t\n\t var warnings = {};\n\t\n\t var titleBarTemplate = BASE_MVC.wrapTemplate([\n\t // adding a tabindex here allows focusing the title bar and the use of keydown to expand the dataset display\n\t '
',\n\t//TODO: prob. belongs in dataset-list-item\n\t '',\n\t '
',\n\t '<%- element.name %>',\n\t '
',\n\t '',\n\t '
'\n\t ], 'element' );\n\t\n\t var subtitleTemplate = BASE_MVC.wrapTemplate([\n\t // override this\n\t ''\n\t ]);\n\t\n\t var detailsTemplate = BASE_MVC.wrapTemplate([\n\t // override this\n\t ''\n\t ]);\n\t\n\t return {\n\t el : elTemplato,\n\t warnings : warnings,\n\t titleBar : titleBarTemplate,\n\t subtitle : subtitleTemplate,\n\t details : detailsTemplate\n\t };\n\t}());\n\t\n\t\n\t//==============================================================================\n\t/** A view that is displayed in some larger list/grid/collection.\n\t * *AND* can display some sub-list of it's own when expanded (e.g. dataset collections).\n\t * This list will 'foldout' when the item is expanded depending on this.foldoutStyle:\n\t * If 'foldout': will expand vertically to show the nested list\n\t * If 'drilldown': will overlay the parent list\n\t *\n\t * Inherits from ListItemView.\n\t *\n\t * _renderDetails does the work of creating this.details: a sub-view that shows the nested list\n\t */\n\tvar FoldoutListItemView = ListItemView.extend({\n\t\n\t /** If 'foldout': show the sub-panel inside the expanded item\n\t * If 'drilldown': only fire events and handle by pub-sub\n\t * (allow the panel containing this item to attach it, hide itself, etc.)\n\t */\n\t foldoutStyle : 'foldout',\n\t /** Panel view class to instantiate for the sub-panel */\n\t foldoutPanelClass : null,\n\t\n\t /** override to:\n\t * add attributes foldoutStyle and foldoutPanelClass for config poly\n\t * disrespect attributes.expanded if drilldown\n\t */\n\t initialize : function( attributes ){\n\t//TODO: hackish\n\t if( this.foldoutStyle === 'drilldown' ){ this.expanded = false; }\n\t this.foldoutStyle = attributes.foldoutStyle || this.foldoutStyle;\n\t this.foldoutPanelClass = attributes.foldoutPanelClass || this.foldoutPanelClass;\n\t\n\t ListItemView.prototype.initialize.call( this, attributes );\n\t this.foldout = this._createFoldoutPanel();\n\t },\n\t\n\t//TODO:?? override to exclude foldout scope?\n\t //$ : function( selector ){\n\t // var $found = ListItemView.prototype.$.call( this, selector );\n\t // return $found;\n\t //},\n\t\n\t /** in this override, attach the foldout panel when rendering details */\n\t _renderDetails : function(){\n\t//TODO: hackish\n\t if( this.foldoutStyle === 'drilldown' ){ return $(); }\n\t var $newDetails = ListItemView.prototype._renderDetails.call( this );\n\t return this._attachFoldout( this.foldout, $newDetails );\n\t },\n\t\n\t /** In this override, handle collection expansion. */\n\t _createFoldoutPanel : function(){\n\t var model = this.model;\n\t var FoldoutClass = this._getFoldoutPanelClass( model ),\n\t options = this._getFoldoutPanelOptions( model ),\n\t foldout = new FoldoutClass( _.extend( options, {\n\t model : model\n\t }));\n\t return foldout;\n\t },\n\t\n\t /** Stub to return proper foldout panel class */\n\t _getFoldoutPanelClass : function(){\n\t // override\n\t return this.foldoutPanelClass;\n\t },\n\t\n\t /** Stub to return proper foldout panel options */\n\t _getFoldoutPanelOptions : function(){\n\t return {\n\t // propagate foldout style down\n\t foldoutStyle : this.foldoutStyle,\n\t fxSpeed : this.fxSpeed\n\t };\n\t },\n\t\n\t /** Render the foldout panel inside the view, hiding controls */\n\t _attachFoldout : function( foldout, $whereTo ){\n\t $whereTo = $whereTo || this.$( '> .details' );\n\t this.foldout = foldout.render( 0 );\n\t//TODO: hack\n\t foldout.$( '> .controls' ).hide();\n\t return $whereTo.append( foldout.$el );\n\t },\n\t\n\t /** In this override, branch on foldoutStyle to show expanded */\n\t expand : function(){\n\t var view = this;\n\t return view._fetchModelDetails()\n\t .always(function(){\n\t if( view.foldoutStyle === 'foldout' ){\n\t view._expand();\n\t } else if( view.foldoutStyle === 'drilldown' ){\n\t view._expandByDrilldown();\n\t }\n\t });\n\t },\n\t\n\t /** For drilldown, set up close handler and fire expanded:drilldown\n\t * containing views can listen to this and handle other things\n\t * (like hiding themselves) by listening for expanded/collapsed:drilldown\n\t */\n\t _expandByDrilldown : function(){\n\t var view = this;\n\t // attachment and rendering done by listener\n\t view.listenTo( view.foldout, 'close', function(){\n\t view.trigger( 'collapsed:drilldown', view, view.foldout );\n\t });\n\t view.trigger( 'expanded:drilldown', view, view.foldout );\n\t }\n\t\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tFoldoutListItemView.prototype.templates = (function(){\n\t\n\t//TODO:?? unnecessary?\n\t // use element identifier\n\t var detailsTemplate = BASE_MVC.wrapTemplate([\n\t '
',\n\t // override with more info (that goes above the panel)\n\t '
'\n\t ], 'collection' );\n\t\n\t return _.extend( {}, ListItemView.prototype.templates, {\n\t details : detailsTemplate\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t return {\n\t ExpandableView : ExpandableView,\n\t ListItemView : ListItemView,\n\t FoldoutListItemView : FoldoutListItemView\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 43 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function($, _) {/**\n\t This is the base class of the tool form plugin. This class is e.g. inherited by the regular and the workflow tool form.\n\t*/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4), __webpack_require__(55), __webpack_require__(7), __webpack_require__(38),\n\t __webpack_require__(18), __webpack_require__(28)], __WEBPACK_AMD_DEFINE_RESULT__ = function(Utils, Deferred, Ui, FormBase, CitationModel, CitationView) {\n\t return FormBase.extend({\n\t initialize: function(options) {\n\t var self = this;\n\t FormBase.prototype.initialize.call(this, options);\n\t this.deferred = new Deferred();\n\t if (options.inputs) {\n\t this._buildForm(options);\n\t } else {\n\t this.deferred.execute(function(process) {\n\t self._buildModel(process, options, true);\n\t });\n\t }\n\t },\n\t\n\t /** Wait for deferred build processes before removal */\n\t remove: function() {\n\t var self = this;\n\t this.$el.hide();\n\t this.deferred.execute(function(){\n\t FormBase.prototype.remove.call(self);\n\t Galaxy.emit.debug('tool-form-base::remove()', 'Destroy view.');\n\t });\n\t },\n\t\n\t /** Build form */\n\t _buildForm: function(options) {\n\t var self = this;\n\t this.options = Utils.merge(options, this.options);\n\t this.options = Utils.merge({\n\t icon : options.icon,\n\t title : '' + options.name + ' ' + options.description + ' (Galaxy Version ' + options.version + ')',\n\t operations : this._operations(),\n\t onchange : function() {\n\t self.deferred.reset();\n\t self.deferred.execute(function(process) {\n\t self._updateModel(process);\n\t });\n\t }\n\t }, this.options);\n\t this.options.customize && this.options.customize( this.options );\n\t this.render();\n\t if ( !this.options.collapsible ) {\n\t this.$el.append( $( '' ).addClass( 'ui-margin-top-large' ).append( this._footer() ) );\n\t }\n\t },\n\t\n\t /** Builds a new model through api call and recreates the entire form\n\t */\n\t _buildModel: function(process, options, hide_message) {\n\t var self = this;\n\t this.options.id = options.id;\n\t this.options.version = options.version;\n\t\n\t // build request url\n\t var build_url = '';\n\t var build_data = {};\n\t if ( options.job_id ) {\n\t build_url = Galaxy.root + 'api/jobs/' + options.job_id + '/build_for_rerun';\n\t } else {\n\t build_url = Galaxy.root + 'api/tools/' + options.id + '/build';\n\t if ( Galaxy.params && Galaxy.params.tool_id == options.id ) {\n\t build_data = $.extend( {}, Galaxy.params );\n\t options.version && ( build_data[ 'tool_version' ] = options.version );\n\t }\n\t }\n\t\n\t // get initial model\n\t Utils.get({\n\t url : build_url,\n\t data : build_data,\n\t success : function(new_model) {\n\t new_model = new_model.tool_model || new_model;\n\t if( !new_model.display ) {\n\t window.location = Galaxy.root;\n\t return;\n\t }\n\t self._buildForm(new_model);\n\t !hide_message && self.message.update({\n\t status : 'success',\n\t message : 'Now you are using \\'' + self.options.name + '\\' version ' + self.options.version + ', id \\'' + self.options.id + '\\'.',\n\t persistent : false\n\t });\n\t Galaxy.emit.debug('tool-form-base::initialize()', 'Initial tool model ready.', new_model);\n\t process.resolve();\n\t },\n\t error : function(response, xhr) {\n\t var error_message = ( response && response.err_msg ) || 'Uncaught error.';\n\t if ( xhr.status == 401 ) {\n\t window.location = Galaxy.root + 'user/login?' + $.param({ redirect : Galaxy.root + '?tool_id=' + self.options.id });\n\t } else if ( self.$el.is(':empty') ) {\n\t self.$el.prepend((new Ui.Message({\n\t message : error_message,\n\t status : 'danger',\n\t persistent : true,\n\t large : true\n\t })).$el);\n\t } else {\n\t Galaxy.modal && Galaxy.modal.show({\n\t title : 'Tool request failed',\n\t body : error_message,\n\t buttons : {\n\t 'Close' : function() {\n\t Galaxy.modal.hide();\n\t }\n\t }\n\t });\n\t }\n\t Galaxy.emit.debug('tool-form::initialize()', 'Initial tool model request failed.', response);\n\t process.reject();\n\t }\n\t });\n\t },\n\t\n\t /** Request a new model for an already created tool form and updates the form inputs\n\t */\n\t _updateModel: function(process) {\n\t // link this\n\t var self = this;\n\t var model_url = this.options.update_url || Galaxy.root + 'api/tools/' + this.options.id + '/build';\n\t var current_state = {\n\t tool_id : this.options.id,\n\t tool_version : this.options.version,\n\t inputs : $.extend(true, {}, self.data.create())\n\t }\n\t this.wait(true);\n\t\n\t // log tool state\n\t Galaxy.emit.debug('tool-form-base::_updateModel()', 'Sending current state.', current_state);\n\t\n\t // post job\n\t Utils.request({\n\t type : 'POST',\n\t url : model_url,\n\t data : current_state,\n\t success : function(new_model) {\n\t self.update(new_model['tool_model'] || new_model);\n\t self.options.update && self.options.update(new_model);\n\t self.wait(false);\n\t Galaxy.emit.debug('tool-form-base::_updateModel()', 'Received new model.', new_model);\n\t process.resolve();\n\t },\n\t error : function(response) {\n\t Galaxy.emit.debug('tool-form-base::_updateModel()', 'Refresh request failed.', response);\n\t process.reject();\n\t }\n\t });\n\t },\n\t\n\t /** Create tool operation menu\n\t */\n\t _operations: function() {\n\t var self = this;\n\t var options = this.options;\n\t\n\t // button for version selection\n\t var versions_button = new Ui.ButtonMenu({\n\t icon : 'fa-cubes',\n\t title : (!options.narrow && 'Versions') || null,\n\t tooltip : 'Select another tool version'\n\t });\n\t if (!options.sustain_version && options.versions && options.versions.length > 1) {\n\t for (var i in options.versions) {\n\t var version = options.versions[i];\n\t if (version != options.version) {\n\t versions_button.addMenu({\n\t title : 'Switch to ' + version,\n\t version : version,\n\t icon : 'fa-cube',\n\t onclick : function() {\n\t // here we update the tool version (some tools encode the version also in the id)\n\t var id = options.id.replace(options.version, this.version);\n\t var version = this.version;\n\t // queue model request\n\t self.deferred.reset();\n\t self.deferred.execute(function(process) {\n\t self._buildModel(process, {id: id, version: version})\n\t });\n\t }\n\t });\n\t }\n\t }\n\t } else {\n\t versions_button.$el.hide();\n\t }\n\t\n\t // button for options e.g. search, help\n\t var menu_button = new Ui.ButtonMenu({\n\t icon : 'fa-caret-down',\n\t title : (!options.narrow && 'Options') || null,\n\t tooltip : 'View available options'\n\t });\n\t if(options.biostar_url) {\n\t menu_button.addMenu({\n\t icon : 'fa-question-circle',\n\t title : 'Question?',\n\t tooltip : 'Ask a question about this tool (Biostar)',\n\t onclick : function() {\n\t window.open(options.biostar_url + '/p/new/post/');\n\t }\n\t });\n\t menu_button.addMenu({\n\t icon : 'fa-search',\n\t title : 'Search',\n\t tooltip : 'Search help for this tool (Biostar)',\n\t onclick : function() {\n\t window.open(options.biostar_url + '/local/search/page/?q=' + options.name);\n\t }\n\t });\n\t };\n\t menu_button.addMenu({\n\t icon : 'fa-share',\n\t title : 'Share',\n\t tooltip : 'Share this tool',\n\t onclick : function() {\n\t prompt('Copy to clipboard: Ctrl+C, Enter', window.location.origin + Galaxy.root + 'root?tool_id=' + options.id);\n\t }\n\t });\n\t\n\t // add admin operations\n\t if (Galaxy.user && Galaxy.user.get('is_admin')) {\n\t menu_button.addMenu({\n\t icon : 'fa-download',\n\t title : 'Download',\n\t tooltip : 'Download this tool',\n\t onclick : function() {\n\t window.location.href = Galaxy.root + 'api/tools/' + options.id + '/download';\n\t }\n\t });\n\t }\n\t\n\t // button for version selection\n\t if (options.requirements && options.requirements.length > 0) {\n\t menu_button.addMenu({\n\t icon : 'fa-info-circle',\n\t title : 'Requirements',\n\t tooltip : 'Display tool requirements',\n\t onclick : function() {\n\t if (!this.visible || self.portlet.collapsed ) {\n\t this.visible = true;\n\t self.portlet.expand();\n\t self.message.update({\n\t persistent : true,\n\t message : self._templateRequirements(options),\n\t status : 'info'\n\t });\n\t } else {\n\t this.visible = false;\n\t self.message.update({\n\t message : ''\n\t });\n\t }\n\t }\n\t });\n\t }\n\t\n\t // add toolshed url\n\t if (options.sharable_url) {\n\t menu_button.addMenu({\n\t icon : 'fa-external-link',\n\t title : 'See in Tool Shed',\n\t tooltip : 'Access the repository',\n\t onclick : function() {\n\t window.open(options.sharable_url);\n\t }\n\t });\n\t }\n\t\n\t return {\n\t menu : menu_button,\n\t versions : versions_button\n\t }\n\t },\n\t\n\t /** Create footer\n\t */\n\t _footer: function() {\n\t var options = this.options;\n\t var $el = $( '' ).append( this._templateHelp( options ) );\n\t if ( options.citations ) {\n\t var $citations = $( '' );\n\t var citations = new CitationModel.ToolCitationCollection();\n\t citations.tool_id = options.id;\n\t var citation_list_view = new CitationView.CitationListView({ el: $citations, collection: citations });\n\t citation_list_view.render();\n\t citations.fetch();\n\t $el.append( $citations );\n\t }\n\t return $el;\n\t },\n\t\n\t /** Templates\n\t */\n\t _templateHelp: function( options ) {\n\t var $tmpl = $( '' ).addClass( 'ui-form-help' ).append( options.help );\n\t $tmpl.find( 'a' ).attr( 'target', '_blank' );\n\t return $tmpl;\n\t },\n\t\n\t _templateRequirements: function( options ) {\n\t var nreq = options.requirements.length;\n\t if ( nreq > 0 ) {\n\t var requirements_message = 'This tool requires ';\n\t _.each( options.requirements, function( req, i ) {\n\t requirements_message += req.name + ( req.version ? ' (Version ' + req.version + ')' : '' ) + ( i < nreq - 2 ? ', ' : ( i == nreq - 2 ? ' and ' : '' ) );\n\t });\n\t var requirements_link = $( '' ).attr( 'target', '_blank' ).attr( 'href', 'https://wiki.galaxyproject.org/Tools/Requirements' ).text( 'here' );\n\t return $( '' ).append( requirements_message + '. Click ' ).append( requirements_link ).append( ' for more information.' );\n\t }\n\t return 'No requirements found.';\n\t }\n\t });\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 44 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $) {/**\n\t * Model, view, and controller objects for Galaxy tools and tool panel.\n\t */\n\t\n\t !(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(2),\n\t __webpack_require__(17),\n\t __webpack_require__(12),\n\t __webpack_require__(19)\n\t\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function(_, util, data, ToolForm) {\n\t 'use strict';\n\t\n\t/**\n\t * Mixin for tracking model visibility.\n\t */\n\tvar VisibilityMixin = {\n\t hidden: false,\n\t\n\t show: function() {\n\t this.set(\"hidden\", false);\n\t },\n\t\n\t hide: function() {\n\t this.set(\"hidden\", true);\n\t },\n\t\n\t toggle: function() {\n\t this.set(\"hidden\", !this.get(\"hidden\"));\n\t },\n\t\n\t is_visible: function() {\n\t return !this.attributes.hidden;\n\t }\n\t\n\t};\n\t\n\t/**\n\t * A tool parameter.\n\t */\n\tvar ToolParameter = Backbone.Model.extend({\n\t defaults: {\n\t name: null,\n\t label: null,\n\t type: null,\n\t value: null,\n\t html: null,\n\t num_samples: 5\n\t },\n\t\n\t initialize: function(options) {\n\t this.attributes.html = unescape(this.attributes.html);\n\t },\n\t\n\t copy: function() {\n\t return new ToolParameter(this.toJSON());\n\t },\n\t\n\t set_value: function(value) {\n\t this.set('value', value || '');\n\t }\n\t});\n\t\n\tvar ToolParameterCollection = Backbone.Collection.extend({\n\t model: ToolParameter\n\t});\n\t\n\t/**\n\t * A data tool parameter.\n\t */\n\tvar DataToolParameter = ToolParameter.extend({});\n\t\n\t/**\n\t * An integer tool parameter.\n\t */\n\tvar IntegerToolParameter = ToolParameter.extend({\n\t set_value: function(value) {\n\t this.set('value', parseInt(value, 10));\n\t },\n\t\n\t /**\n\t * Returns samples from a tool input.\n\t */\n\t get_samples: function() {\n\t return d3.scale.linear()\n\t .domain([this.get('min'), this.get('max')])\n\t .ticks(this.get('num_samples'));\n\t }\n\t});\n\t\n\tvar FloatToolParameter = IntegerToolParameter.extend({\n\t set_value: function(value) {\n\t this.set('value', parseFloat(value));\n\t }\n\t});\n\t\n\t/**\n\t * A select tool parameter.\n\t */\n\tvar SelectToolParameter = ToolParameter.extend({\n\t /**\n\t * Returns tool options.\n\t */\n\t get_samples: function() {\n\t return _.map(this.get('options'), function(option) {\n\t return option[0];\n\t });\n\t }\n\t});\n\t\n\t// Set up dictionary of parameter types.\n\tToolParameter.subModelTypes = {\n\t 'integer': IntegerToolParameter,\n\t 'float': FloatToolParameter,\n\t 'data': DataToolParameter,\n\t 'select': SelectToolParameter\n\t};\n\t\n\t/**\n\t * A Galaxy tool.\n\t */\n\tvar Tool = Backbone.Model.extend({\n\t // Default attributes.\n\t defaults: {\n\t id: null,\n\t name: null,\n\t description: null,\n\t target: null,\n\t inputs: [],\n\t outputs: []\n\t },\n\t\n\t urlRoot: Galaxy.root + 'api/tools',\n\t\n\t initialize: function(options) {\n\t\n\t // Set parameters.\n\t this.set('inputs', new ToolParameterCollection(_.map(options.inputs, function(p) {\n\t var p_class = ToolParameter.subModelTypes[p.type] || ToolParameter;\n\t return new p_class(p);\n\t })));\n\t },\n\t\n\t /**\n\t *\n\t */\n\t toJSON: function() {\n\t var rval = Backbone.Model.prototype.toJSON.call(this);\n\t\n\t // Convert inputs to JSON manually.\n\t rval.inputs = this.get('inputs').map(function(i) { return i.toJSON(); });\n\t return rval;\n\t },\n\t\n\t /**\n\t * Removes inputs of a particular type; this is useful because not all inputs can be handled by\n\t * client and server yet.\n\t */\n\t remove_inputs: function(types) {\n\t var tool = this,\n\t incompatible_inputs = tool.get('inputs').filter( function(input) {\n\t return ( types.indexOf( input.get('type') ) !== -1);\n\t });\n\t tool.get('inputs').remove(incompatible_inputs);\n\t },\n\t\n\t /**\n\t * Returns object copy, optionally including only inputs that can be sampled.\n\t */\n\t copy: function(only_samplable_inputs) {\n\t var copy = new Tool(this.toJSON());\n\t\n\t // Return only samplable inputs if flag is set.\n\t if (only_samplable_inputs) {\n\t var valid_inputs = new Backbone.Collection();\n\t copy.get('inputs').each(function(input) {\n\t if (input.get_samples()) {\n\t valid_inputs.push(input);\n\t }\n\t });\n\t copy.set('inputs', valid_inputs);\n\t }\n\t\n\t return copy;\n\t },\n\t\n\t apply_search_results: function(results) {\n\t ( _.indexOf(results, this.attributes.id) !== -1 ? this.show() : this.hide() );\n\t return this.is_visible();\n\t },\n\t\n\t /**\n\t * Set a tool input's value.\n\t */\n\t set_input_value: function(name, value) {\n\t this.get('inputs').find(function(input) {\n\t return input.get('name') === name;\n\t }).set('value', value);\n\t },\n\t\n\t /**\n\t * Set many input values at once.\n\t */\n\t set_input_values: function(inputs_dict) {\n\t var self = this;\n\t _.each(_.keys(inputs_dict), function(input_name) {\n\t self.set_input_value(input_name, inputs_dict[input_name]);\n\t });\n\t },\n\t\n\t /**\n\t * Run tool; returns a Deferred that resolves to the tool's output(s).\n\t */\n\t run: function() {\n\t return this._run();\n\t },\n\t\n\t /**\n\t * Rerun tool using regions and a target dataset.\n\t */\n\t rerun: function(target_dataset, regions) {\n\t return this._run({\n\t action: 'rerun',\n\t target_dataset_id: target_dataset.id,\n\t regions: regions\n\t });\n\t },\n\t\n\t /**\n\t * Returns input dict for tool's inputs.\n\t */\n\t get_inputs_dict: function() {\n\t var input_dict = {};\n\t this.get('inputs').each(function(input) {\n\t input_dict[input.get('name')] = input.get('value');\n\t });\n\t return input_dict;\n\t },\n\t\n\t /**\n\t * Run tool; returns a Deferred that resolves to the tool's output(s).\n\t * NOTE: this method is a helper method and should not be called directly.\n\t */\n\t _run: function(additional_params) {\n\t // Create payload.\n\t var payload = _.extend({\n\t tool_id: this.id,\n\t inputs: this.get_inputs_dict()\n\t }, additional_params);\n\t\n\t // Because job may require indexing datasets, use server-side\n\t // deferred to ensure that job is run. Also use deferred that\n\t // resolves to outputs from tool.\n\t var run_deferred = $.Deferred(),\n\t ss_deferred = new util.ServerStateDeferred({\n\t ajax_settings: {\n\t url: this.urlRoot,\n\t data: JSON.stringify(payload),\n\t dataType: \"json\",\n\t contentType: 'application/json',\n\t type: \"POST\"\n\t },\n\t interval: 2000,\n\t success_fn: function(response) {\n\t return response !== \"pending\";\n\t }\n\t });\n\t\n\t // Run job and resolve run_deferred to tool outputs.\n\t $.when(ss_deferred.go()).then(function(result) {\n\t run_deferred.resolve(new data.DatasetCollection(result));\n\t });\n\t return run_deferred;\n\t }\n\t});\n\t_.extend(Tool.prototype, VisibilityMixin);\n\t\n\t/**\n\t * Tool view.\n\t */\n\tvar ToolView = Backbone.View.extend({\n\t\n\t});\n\t\n\t/**\n\t * Wrap collection of tools for fast access/manipulation.\n\t */\n\tvar ToolCollection = Backbone.Collection.extend({\n\t model: Tool\n\t});\n\t\n\t/**\n\t * Label or section header in tool panel.\n\t */\n\tvar ToolSectionLabel = Backbone.Model.extend(VisibilityMixin);\n\t\n\t/**\n\t * Section of tool panel with elements (labels and tools).\n\t */\n\tvar ToolSection = Backbone.Model.extend({\n\t defaults: {\n\t elems: [],\n\t open: false\n\t },\n\t\n\t clear_search_results: function() {\n\t _.each(this.attributes.elems, function(elt) {\n\t elt.show();\n\t });\n\t\n\t this.show();\n\t this.set(\"open\", false);\n\t },\n\t\n\t apply_search_results: function(results) {\n\t var all_hidden = true,\n\t cur_label;\n\t _.each(this.attributes.elems, function(elt) {\n\t if (elt instanceof ToolSectionLabel) {\n\t cur_label = elt;\n\t cur_label.hide();\n\t }\n\t else if (elt instanceof Tool) {\n\t if (elt.apply_search_results(results)) {\n\t all_hidden = false;\n\t if (cur_label) {\n\t cur_label.show();\n\t }\n\t }\n\t }\n\t });\n\t\n\t if (all_hidden) {\n\t this.hide();\n\t }\n\t else {\n\t this.show();\n\t this.set(\"open\", true);\n\t }\n\t }\n\t});\n\t_.extend(ToolSection.prototype, VisibilityMixin);\n\t\n\t/**\n\t * Tool search that updates results when query is changed. Result value of null\n\t * indicates that query was not run; if not null, results are from search using\n\t * query.\n\t */\n\tvar ToolSearch = Backbone.Model.extend({\n\t defaults: {\n\t search_hint_string: \"search tools\",\n\t min_chars_for_search: 3,\n\t clear_btn_url: \"\",\n\t search_url: \"\",\n\t visible: true,\n\t query: \"\",\n\t results: null,\n\t // ESC (27) will clear the input field and tool search filters\n\t clear_key: 27\n\t },\n\t\n\t urlRoot: Galaxy.root + 'api/tools',\n\t\n\t initialize: function() {\n\t this.on(\"change:query\", this.do_search);\n\t },\n\t\n\t /**\n\t * Do the search and update the results.\n\t */\n\t do_search: function() {\n\t var query = this.attributes.query;\n\t\n\t // If query is too short, do not search.\n\t if (query.length < this.attributes.min_chars_for_search) {\n\t this.set(\"results\", null);\n\t return;\n\t }\n\t\n\t // Do search via AJAX.\n\t var q = query;\n\t // Stop previous ajax-request\n\t if (this.timer) {\n\t clearTimeout(this.timer);\n\t }\n\t // Start a new ajax-request in X ms\n\t $(\"#search-clear-btn\").hide();\n\t $(\"#search-spinner\").show();\n\t var self = this;\n\t this.timer = setTimeout(function () {\n\t // log the search to analytics if present\n\t if ( typeof ga !== 'undefined' ) {\n\t ga( 'send', 'pageview', Galaxy.root + '?q=' + q );\n\t }\n\t $.get( self.urlRoot, { q: q }, function (data) {\n\t self.set(\"results\", data);\n\t $(\"#search-spinner\").hide();\n\t $(\"#search-clear-btn\").show();\n\t }, \"json\" );\n\t }, 400 );\n\t },\n\t\n\t clear_search: function() {\n\t this.set(\"query\", \"\");\n\t this.set(\"results\", null);\n\t }\n\t\n\t});\n\t_.extend(ToolSearch.prototype, VisibilityMixin);\n\t\n\t/**\n\t * Tool Panel.\n\t */\n\tvar ToolPanel = Backbone.Model.extend({\n\t\n\t initialize: function(options) {\n\t this.attributes.tool_search = options.tool_search;\n\t this.attributes.tool_search.on(\"change:results\", this.apply_search_results, this);\n\t this.attributes.tools = options.tools;\n\t this.attributes.layout = new Backbone.Collection( this.parse(options.layout) );\n\t },\n\t\n\t /**\n\t * Parse tool panel dictionary and return collection of tool panel elements.\n\t */\n\t parse: function(response) {\n\t // Recursive function to parse tool panel elements.\n\t var self = this,\n\t // Helper to recursively parse tool panel.\n\t parse_elt = function(elt_dict) {\n\t var type = elt_dict.model_class;\n\t // There are many types of tools; for now, anything that ends in 'Tool'\n\t // is treated as a generic tool.\n\t if ( type.indexOf('Tool') === type.length - 4 ) {\n\t return self.attributes.tools.get(elt_dict.id);\n\t }\n\t else if (type === 'ToolSection') {\n\t // Parse elements.\n\t var elems = _.map(elt_dict.elems, parse_elt);\n\t elt_dict.elems = elems;\n\t return new ToolSection(elt_dict);\n\t }\n\t else if (type === 'ToolSectionLabel') {\n\t return new ToolSectionLabel(elt_dict);\n\t }\n\t };\n\t\n\t return _.map(response, parse_elt);\n\t },\n\t\n\t clear_search_results: function() {\n\t this.get('layout').each(function(panel_elt) {\n\t if (panel_elt instanceof ToolSection) {\n\t panel_elt.clear_search_results();\n\t }\n\t else {\n\t // Label or tool, so just show.\n\t panel_elt.show();\n\t }\n\t });\n\t },\n\t\n\t apply_search_results: function() {\n\t var results = this.get('tool_search').get('results');\n\t if (results === null) {\n\t this.clear_search_results();\n\t return;\n\t }\n\t\n\t var cur_label = null;\n\t this.get('layout').each(function(panel_elt) {\n\t if (panel_elt instanceof ToolSectionLabel) {\n\t cur_label = panel_elt;\n\t cur_label.hide();\n\t }\n\t else if (panel_elt instanceof Tool) {\n\t if (panel_elt.apply_search_results(results)) {\n\t if (cur_label) {\n\t cur_label.show();\n\t }\n\t }\n\t }\n\t else {\n\t // Starting new section, so clear current label.\n\t cur_label = null;\n\t panel_elt.apply_search_results(results);\n\t }\n\t });\n\t }\n\t});\n\t\n\t/**\n\t * View classes for Galaxy tools and tool panel.\n\t *\n\t * Views use the templates defined below for rendering. Views update as needed\n\t * based on (a) model/collection events and (b) user interactions; in this sense,\n\t * they are controllers are well and the HTML is the real view in the MVC architecture.\n\t */\n\t\n\t/**\n\t * Base view that handles visibility based on model's hidden attribute.\n\t */\n\tvar BaseView = Backbone.View.extend({\n\t initialize: function() {\n\t this.model.on(\"change:hidden\", this.update_visible, this);\n\t this.update_visible();\n\t },\n\t update_visible: function() {\n\t ( this.model.attributes.hidden ? this.$el.hide() : this.$el.show() );\n\t }\n\t});\n\t\n\t/**\n\t * Link to a tool.\n\t */\n\tvar ToolLinkView = BaseView.extend({\n\t tagName: 'div',\n\t\n\t render: function() {\n\t // create element\n\t var $link = $('');\n\t $link.append(templates.tool_link(this.model.toJSON()));\n\t\n\t var formStyle = this.model.get( 'form_style', null );\n\t // open upload dialog for upload tool\n\t if (this.model.id === 'upload1') {\n\t $link.find('a').on('click', function(e) {\n\t e.preventDefault();\n\t Galaxy.upload.show();\n\t });\n\t }\n\t else if ( formStyle === 'regular' ) { // regular tools\n\t var self = this;\n\t $link.find('a').on('click', function(e) {\n\t e.preventDefault();\n\t var form = new ToolForm.View( { id : self.model.id, version : self.model.get('version') } );\n\t form.deferred.execute(function() {\n\t Galaxy.app.display( form );\n\t });\n\t });\n\t }\n\t\n\t // add element\n\t this.$el.append($link);\n\t return this;\n\t }\n\t});\n\t\n\t/**\n\t * Panel label/section header.\n\t */\n\tvar ToolSectionLabelView = BaseView.extend({\n\t tagName: 'div',\n\t className: 'toolPanelLabel',\n\t\n\t render: function() {\n\t this.$el.append( $(\"\").text(this.model.attributes.text) );\n\t return this;\n\t }\n\t});\n\t\n\t/**\n\t * Panel section.\n\t */\n\tvar ToolSectionView = BaseView.extend({\n\t tagName: 'div',\n\t className: 'toolSectionWrapper',\n\t\n\t initialize: function() {\n\t BaseView.prototype.initialize.call(this);\n\t this.model.on(\"change:open\", this.update_open, this);\n\t },\n\t\n\t render: function() {\n\t // Build using template.\n\t this.$el.append( templates.panel_section(this.model.toJSON()) );\n\t\n\t // Add tools to section.\n\t var section_body = this.$el.find(\".toolSectionBody\");\n\t _.each(this.model.attributes.elems, function(elt) {\n\t if (elt instanceof Tool) {\n\t var tool_view = new ToolLinkView({model: elt, className: \"toolTitle\"});\n\t tool_view.render();\n\t section_body.append(tool_view.$el);\n\t }\n\t else if (elt instanceof ToolSectionLabel) {\n\t var label_view = new ToolSectionLabelView({model: elt});\n\t label_view.render();\n\t section_body.append(label_view.$el);\n\t }\n\t else {\n\t // TODO: handle nested section bodies?\n\t }\n\t });\n\t return this;\n\t },\n\t\n\t events: {\n\t 'click .toolSectionTitle > a': 'toggle'\n\t },\n\t\n\t /**\n\t * Toggle visibility of tool section.\n\t */\n\t toggle: function() {\n\t this.model.set(\"open\", !this.model.attributes.open);\n\t },\n\t\n\t /**\n\t * Update whether section is open or close.\n\t */\n\t update_open: function() {\n\t (this.model.attributes.open ?\n\t this.$el.children(\".toolSectionBody\").slideDown(\"fast\") :\n\t this.$el.children(\".toolSectionBody\").slideUp(\"fast\")\n\t );\n\t }\n\t});\n\t\n\tvar ToolSearchView = Backbone.View.extend({\n\t tagName: 'div',\n\t id: 'tool-search',\n\t className: 'bar',\n\t\n\t events: {\n\t 'click': 'focus_and_select',\n\t 'keyup :input': 'query_changed',\n\t 'click #search-clear-btn': 'clear'\n\t },\n\t\n\t render: function() {\n\t this.$el.append( templates.tool_search(this.model.toJSON()) );\n\t if (!this.model.is_visible()) {\n\t this.$el.hide();\n\t }\n\t this.$el.find('[title]').tooltip();\n\t return this;\n\t },\n\t\n\t focus_and_select: function() {\n\t this.$el.find(\":input\").focus().select();\n\t },\n\t\n\t clear: function() {\n\t this.model.clear_search();\n\t this.$el.find(\":input\").val('');\n\t this.focus_and_select();\n\t return false;\n\t },\n\t\n\t query_changed: function( evData ) {\n\t // check for the 'clear key' (ESC) first\n\t if( ( this.model.attributes.clear_key ) &&\n\t ( this.model.attributes.clear_key === evData.which ) ){\n\t this.clear();\n\t return false;\n\t }\n\t this.model.set(\"query\", this.$el.find(\":input\").val());\n\t }\n\t});\n\t\n\t/**\n\t * Tool panel view. Events triggered include:\n\t * tool_link_click(click event, tool_model)\n\t */\n\tvar ToolPanelView = Backbone.View.extend({\n\t tagName: 'div',\n\t className: 'toolMenu',\n\t\n\t /**\n\t * Set up view.\n\t */\n\t initialize: function() {\n\t this.model.get('tool_search').on(\"change:results\", this.handle_search_results, this);\n\t },\n\t\n\t render: function() {\n\t var self = this;\n\t\n\t // Render search.\n\t var search_view = new ToolSearchView( { model: this.model.get('tool_search') } );\n\t search_view.render();\n\t self.$el.append(search_view.$el);\n\t\n\t // Render panel.\n\t this.model.get('layout').each(function(panel_elt) {\n\t if (panel_elt instanceof ToolSection) {\n\t var section_title_view = new ToolSectionView({model: panel_elt});\n\t section_title_view.render();\n\t self.$el.append(section_title_view.$el);\n\t }\n\t else if (panel_elt instanceof Tool) {\n\t var tool_view = new ToolLinkView({model: panel_elt, className: \"toolTitleNoSection\"});\n\t tool_view.render();\n\t self.$el.append(tool_view.$el);\n\t }\n\t else if (panel_elt instanceof ToolSectionLabel) {\n\t var label_view = new ToolSectionLabelView({model: panel_elt});\n\t label_view.render();\n\t self.$el.append(label_view.$el);\n\t }\n\t });\n\t\n\t // Setup tool link click eventing.\n\t self.$el.find(\"a.tool-link\").click(function(e) {\n\t // Tool id is always the first class.\n\t var\n\t tool_id = $(this).attr('class').split(/\\s+/)[0],\n\t tool = self.model.get('tools').get(tool_id);\n\t\n\t self.trigger(\"tool_link_click\", e, tool);\n\t });\n\t\n\t return this;\n\t },\n\t\n\t handle_search_results: function() {\n\t var results = this.model.get('tool_search').get('results');\n\t if (results && results.length === 0) {\n\t $(\"#search-no-results\").show();\n\t }\n\t else {\n\t $(\"#search-no-results\").hide();\n\t }\n\t }\n\t});\n\t\n\t/**\n\t * View for working with a tool: setting parameters and inputs and executing the tool.\n\t */\n\tvar ToolFormView = Backbone.View.extend({\n\t className: 'toolForm',\n\t\n\t render: function() {\n\t this.$el.children().remove();\n\t this.$el.append( templates.tool_form(this.model.toJSON()) );\n\t }\n\t});\n\t\n\t/**\n\t * Integrated tool menu + tool execution.\n\t */\n\tvar IntegratedToolMenuAndView = Backbone.View.extend({\n\t className: 'toolMenuAndView',\n\t\n\t initialize: function() {\n\t this.tool_panel_view = new ToolPanelView({collection: this.collection});\n\t this.tool_form_view = new ToolFormView();\n\t },\n\t\n\t render: function() {\n\t // Render and append tool panel.\n\t this.tool_panel_view.render();\n\t this.tool_panel_view.$el.css(\"float\", \"left\");\n\t this.$el.append(this.tool_panel_view.$el);\n\t\n\t // Append tool form view.\n\t this.tool_form_view.$el.hide();\n\t this.$el.append(this.tool_form_view.$el);\n\t\n\t // On tool link click, show tool.\n\t var self = this;\n\t this.tool_panel_view.on(\"tool_link_click\", function(e, tool) {\n\t // Prevents click from activating link:\n\t e.preventDefault();\n\t // Show tool that was clicked on:\n\t self.show_tool(tool);\n\t });\n\t },\n\t\n\t /**\n\t * Fetch and display tool.\n\t */\n\t show_tool: function(tool) {\n\t var self = this;\n\t tool.fetch().done( function() {\n\t self.tool_form_view.model = tool;\n\t self.tool_form_view.render();\n\t self.tool_form_view.$el.show();\n\t $('#left').width(\"650px\");\n\t });\n\t }\n\t});\n\t\n\t// TODO: move into relevant views\n\tvar templates = {\n\t // the search bar at the top of the tool panel\n\t tool_search : _.template([\n\t '\" autocomplete=\"off\" type=\"text\" />',\n\t '',\n\t //TODO: replace with icon\n\t '',\n\t ].join('')),\n\t\n\t // the category level container in the tool panel (e.g. 'Get Data', 'Text Manipulation')\n\t panel_section : _.template([\n\t '
'\n\t ].join('')),\n\t\n\t // a single tool's link in the tool panel; will load the tool form in the center panel\n\t tool_link : _.template([\n\t '',\n\t '<% _.each( labels, function( label ){ %>',\n\t '\">',\n\t '<%- label %>',\n\t '',\n\t '<% }); %>',\n\t '',\n\t ' tool-link\" href=\"<%= link %>\" target=\"<%- target %>\" minsizehint=\"<%- min_width %>\">',\n\t '<%- name %>',\n\t '',\n\t ' <%- description %>'\n\t ].join('')),\n\t\n\t // the tool form for entering tool parameters, viewing help and executing the tool\n\t // loaded when a tool link is clicked in the tool panel\n\t tool_form : _.template([\n\t '
',\n\t // TODO: we need scoping here because 'help' is the dom for the help menu in the masthead\n\t // which implies a leaky variable that I can't find\n\t ].join(''), { variable: 'tool' }),\n\t};\n\t\n\t\n\t// Exports\n\treturn {\n\t ToolParameter: ToolParameter,\n\t IntegerToolParameter: IntegerToolParameter,\n\t SelectToolParameter: SelectToolParameter,\n\t Tool: Tool,\n\t ToolCollection: ToolCollection,\n\t ToolSearch: ToolSearch,\n\t ToolPanel: ToolPanel,\n\t ToolPanelView: ToolPanelView,\n\t ToolFormView: ToolFormView\n\t};\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1)))\n\n/***/ },\n/* 45 */,\n/* 46 */\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 //jquery\n\t //backbone\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function(){\n\t// =============================================================================\n\t/**\n\t * view for a popup menu\n\t */\n\tvar PopupMenu = Backbone.View.extend({\n\t//TODO: maybe better as singleton off the Galaxy obj\n\t /** Cache the desired button element and options, set up the button click handler\n\t * NOTE: attaches this view as HTML/jQ data on the button for later use.\n\t */\n\t initialize: function( $button, options ){\n\t // default settings\n\t this.$button = $button;\n\t if( !this.$button.length ){\n\t this.$button = $( '' );\n\t }\n\t this.options = options || [];\n\t this.$button.data( 'popupmenu', this );\n\t\n\t // set up button click -> open menu behavior\n\t var menu = this;\n\t this.$button.click( function( event ){\n\t // if there's already a menu open, remove it\n\t $( '.popmenu-wrapper' ).remove();\n\t menu._renderAndShow( event );\n\t return false;\n\t });\n\t },\n\t\n\t // render the menu, append to the page body at the click position, and set up the 'click-away' handlers, show\n\t _renderAndShow: function( clickEvent ){\n\t this.render();\n\t this.$el.appendTo( 'body' ).css( this._getShownPosition( clickEvent )).show();\n\t this._setUpCloseBehavior();\n\t },\n\t\n\t // render the menu\n\t // this menu doesn't attach itself to the DOM ( see _renderAndShow )\n\t render: function(){\n\t // render the menu body absolute and hidden, fill with template\n\t this.$el.addClass( 'popmenu-wrapper' ).hide()\n\t .css({ position : 'absolute' })\n\t .html( this.template( this.$button.attr( 'id' ), this.options ));\n\t\n\t // set up behavior on each link/anchor elem\n\t if( this.options.length ){\n\t var menu = this;\n\t //precondition: there should be one option per li\n\t this.$el.find( 'li' ).each( function( i, li ){\n\t var option = menu.options[i];\n\t\n\t // if the option has 'func', call that function when the anchor is clicked\n\t if( option.func ){\n\t $( this ).children( 'a.popupmenu-option' ).click( function( event ){\n\t option.func.call( menu, event, option );\n\t // We must preventDefault otherwise clicking \"cancel\"\n\t // on a purge or something still navigates and causes\n\t // the action.\n\t event.preventDefault();\n\t // bubble up so that an option click will call the close behavior\n\t });\n\t }\n\t });\n\t }\n\t return this;\n\t },\n\t\n\t template : function( id, options ){\n\t return [\n\t '
'\n\t ].join( '' );\n\t }).join( '' );\n\t },\n\t\n\t // get the absolute position/offset for the menu\n\t _getShownPosition : function( clickEvent ){\n\t\n\t // display menu horiz. centered on click...\n\t var menuWidth = this.$el.width();\n\t var x = clickEvent.pageX - menuWidth / 2 ;\n\t\n\t // adjust to handle horiz. scroll and window dimensions ( draw entirely on visible screen area )\n\t x = Math.min( x, $( document ).scrollLeft() + $( window ).width() - menuWidth - 5 );\n\t x = Math.max( x, $( document ).scrollLeft() + 5 );\n\t return {\n\t top: clickEvent.pageY,\n\t left: x\n\t };\n\t },\n\t\n\t // bind an event handler to all available frames so that when anything is clicked\n\t // the menu is removed from the DOM and the event handler unbinds itself\n\t _setUpCloseBehavior: function(){\n\t var menu = this;\n\t//TODO: alternately: focus hack, blocking overlay, jquery.blockui\n\t\n\t // function to close popup and unbind itself\n\t function closePopup( event ){\n\t $( document ).off( 'click.close_popup' );\n\t if( window && window.parent !== window ){\n\t try {\n\t $( window.parent.document ).off( \"click.close_popup\" );\n\t } catch( err ){}\n\t } else {\n\t try {\n\t $( 'iframe#galaxy_main' ).contents().off( \"click.close_popup\" );\n\t } catch( err ){}\n\t }\n\t menu.remove();\n\t }\n\t\n\t $( 'html' ).one( \"click.close_popup\", closePopup );\n\t if( window && window.parent !== window ){\n\t try {\n\t $( window.parent.document ).find( 'html' ).one( \"click.close_popup\", closePopup );\n\t } catch( err ){}\n\t } else {\n\t try {\n\t $( 'iframe#galaxy_main' ).contents().one( \"click.close_popup\", closePopup );\n\t } catch( err ){}\n\t }\n\t },\n\t\n\t // add a menu option/item at the given index\n\t addItem: function( item, index ){\n\t // append to end if no index\n\t index = ( index >= 0 ) ? index : this.options.length;\n\t this.options.splice( index, 0, item );\n\t return this;\n\t },\n\t\n\t // remove a menu option/item at the given index\n\t removeItem: function( index ){\n\t if( index >=0 ){\n\t this.options.splice( index, 1 );\n\t }\n\t return this;\n\t },\n\t\n\t // search for a menu option by its html\n\t findIndexByHtml: function( html ){\n\t for( var i = 0; i < this.options.length; i++ ){\n\t if( _.has( this.options[i], 'html' ) && ( this.options[i].html === html )){\n\t return i;\n\t }\n\t }\n\t return null;\n\t },\n\t\n\t // search for a menu option by its html\n\t findItemByHtml: function( html ){\n\t return this.options[( this.findIndexByHtml( html ))];\n\t },\n\t\n\t // string representation\n\t toString: function(){\n\t return 'PopupMenu';\n\t }\n\t});\n\t/** shortcut to new for when you don't need to preserve the ref */\n\tPopupMenu.create = function _create( $button, options ){\n\t return new PopupMenu( $button, options );\n\t};\n\t\n\t// -----------------------------------------------------------------------------\n\t// the following class functions are bridges from the original make_popupmenu and make_popup_menus\n\t// to the newer backbone.js PopupMenu\n\t\n\t/** Create a PopupMenu from simple map initial_options activated by clicking button_element.\n\t * Converts initial_options to object array used by PopupMenu.\n\t * @param {jQuery|DOMElement} button_element element which, when clicked, activates menu\n\t * @param {Object} initial_options map of key -> values, where\n\t * key is option text, value is fn to call when option is clicked\n\t * @returns {PopupMenu} the PopupMenu created\n\t */\n\tPopupMenu.make_popupmenu = function( button_element, initial_options ){\n\t var convertedOptions = [];\n\t _.each( initial_options, function( optionVal, optionKey ){\n\t var newOption = { html: optionKey };\n\t\n\t // keys with null values indicate: header\n\t if( optionVal === null ){ // !optionVal? (null only?)\n\t newOption.header = true;\n\t\n\t // keys with function values indicate: a menu option\n\t } else if( jQuery.type( optionVal ) === 'function' ){\n\t newOption.func = optionVal;\n\t }\n\t //TODO:?? any other special optionVals?\n\t // there was no divider option originally\n\t convertedOptions.push( newOption );\n\t });\n\t return new PopupMenu( $( button_element ), convertedOptions );\n\t};\n\t\n\t/** Find all anchors in $parent (using selector) and covert anchors into a PopupMenu options map.\n\t * @param {jQuery} $parent the element that contains the links to convert to options\n\t * @param {String} selector jq selector string to find links\n\t * @returns {Object[]} the options array to initialize a PopupMenu\n\t */\n\t//TODO: lose parent and selector, pass in array of links, use map to return options\n\tPopupMenu.convertLinksToOptions = function( $parent, selector ){\n\t $parent = $( $parent );\n\t selector = selector || 'a';\n\t var options = [];\n\t $parent.find( selector ).each( function( elem, i ){\n\t var option = {}, $link = $( elem );\n\t\n\t // convert link text to the option text (html) and the href into the option func\n\t option.html = $link.text();\n\t if( $link.attr( 'href' ) ){\n\t var linkHref = $link.attr( 'href' ),\n\t linkTarget = $link.attr( 'target' ),\n\t confirmText = $link.attr( 'confirm' );\n\t\n\t option.func = function(){\n\t // if there's a \"confirm\" attribute, throw up a confirmation dialog, and\n\t // if the user cancels - do nothing\n\t if( ( confirmText ) && ( !confirm( confirmText ) ) ){ return; }\n\t\n\t // if there's no confirm attribute, or the user accepted the confirm dialog:\n\t switch( linkTarget ){\n\t // relocate the center panel\n\t case '_parent':\n\t window.parent.location = linkHref;\n\t break;\n\t\n\t // relocate the entire window\n\t case '_top':\n\t window.top.location = linkHref;\n\t break;\n\t\n\t // relocate this panel\n\t default:\n\t window.location = linkHref;\n\t }\n\t };\n\t }\n\t options.push( option );\n\t });\n\t return options;\n\t};\n\t\n\t/** Create a single popupmenu from existing DOM button and anchor elements\n\t * @param {jQuery} $buttonElement the element that when clicked will open the menu\n\t * @param {jQuery} $menuElement the element that contains the anchors to convert into a menu\n\t * @param {String} menuElementLinkSelector jq selector string used to find anchors to be made into menu options\n\t * @returns {PopupMenu} the PopupMenu (Backbone View) that can render, control the menu\n\t */\n\tPopupMenu.fromExistingDom = function( $buttonElement, $menuElement, menuElementLinkSelector ){\n\t $buttonElement = $( $buttonElement );\n\t $menuElement = $( $menuElement );\n\t var options = PopupMenu.convertLinksToOptions( $menuElement, menuElementLinkSelector );\n\t // we're done with the menu (having converted it to an options map)\n\t $menuElement.remove();\n\t return new PopupMenu( $buttonElement, options );\n\t};\n\t\n\t/** Create all popupmenus within a document or a more specific element\n\t * @param {DOMElement} parent the DOM element in which to search for popupmenus to build (defaults to document)\n\t * @param {String} menuSelector jq selector string to find popupmenu menu elements (defaults to \"div[popupmenu]\")\n\t * @param {Function} buttonSelectorBuildFn the function to build the jq button selector.\n\t * Will be passed $menuElement, parent.\n\t * (Defaults to return '#' + $menuElement.attr( 'popupmenu' ); )\n\t * @returns {PopupMenu[]} array of popupmenus created\n\t */\n\tPopupMenu.make_popup_menus = function( parent, menuSelector, buttonSelectorBuildFn ){\n\t parent = parent || document;\n\t // orig. Glx popupmenu menus have a (non-std) attribute 'popupmenu'\n\t // which contains the id of the button that activates the menu\n\t menuSelector = menuSelector || 'div[popupmenu]';\n\t // default to (orig. Glx) matching button to menu by using the popupmenu attr of the menu as the id of the button\n\t buttonSelectorBuildFn = buttonSelectorBuildFn || function( $menuElement, parent ){\n\t return '#' + $menuElement.attr( 'popupmenu' );\n\t };\n\t\n\t // aggregate and return all PopupMenus\n\t var popupMenusCreated = [];\n\t $( parent ).find( menuSelector ).each( function(){\n\t var $menuElement = $( this ),\n\t $buttonElement = $( parent ).find( buttonSelectorBuildFn( $menuElement, parent ) );\n\t popupMenusCreated.push( PopupMenu.fromDom( $buttonElement, $menuElement ) );\n\t $buttonElement.addClass( 'popup' );\n\t });\n\t return popupMenusCreated;\n\t};\n\t\n\t\n\t// =============================================================================\n\t return PopupMenu;\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 47 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $) {/** Renders the color picker used e.g. in the tool form **/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4)], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils ) {\n\t return Backbone.View.extend({\n\t colors: {\n\t standard: ['c00000','ff0000','ffc000','ffff00','92d050','00b050','00b0f0','0070c0','002060','7030a0'],\n\t base : ['ffffff','000000','eeece1','1f497d','4f81bd','c0504d','9bbb59','8064a2','4bacc6','f79646'],\n\t theme :[['f2f2f2','7f7f7f','ddd9c3','c6d9f0','dbe5f1','f2dcdb','ebf1dd','e5e0ec','dbeef3','fdeada'],\n\t ['d8d8d8','595959','c4bd97','8db3e2','b8cce4','e5b9b7','d7e3bc','ccc1d9','b7dde8','fbd5b5'],\n\t ['bfbfbf','3f3f3f','938953','548dd4','95b3d7','d99694','c3d69b','b2a2c7','92cddc','fac08f'],\n\t ['a5a5a5','262626','494429','17365d','366092','953734','76923c','5f497a','31859b','e36c09'],\n\t ['7f7f7e','0c0c0c','1d1b10','0f243e','244061','632423','4f6128','3f3151','205867','974806']]\n\t },\n\t initialize : function( options ) {\n\t this.options = Utils.merge( options, {} );\n\t this.setElement( this._template() );\n\t\n\t // link components\n\t this.$panel = this.$( '.ui-color-picker-panel' );\n\t this.$view = this.$( '.ui-color-picker-view' );\n\t this.$value = this.$( '.ui-color-picker-value' );\n\t this.$header = this.$( '.ui-color-picker-header' );\n\t\n\t // build panel\n\t this._build();\n\t\n\t // hide panel on start up\n\t this.visible = false;\n\t\n\t // set initial value\n\t this.value( this.options.value );\n\t\n\t // link boxes\n\t this.$boxes = this.$( '.ui-color-picker-box' );\n\t\n\t // add event handler\n\t var self = this;\n\t this.$boxes.on( 'click', function() {\n\t self.value( $( this ).css( 'background-color' ) );\n\t self.$header.trigger( 'click' );\n\t } );\n\t this.$header.on( 'click', function() {\n\t self.visible = !self.visible;\n\t if ( self.visible ) {\n\t self.$view.fadeIn( 'fast' );\n\t } else {\n\t self.$view.fadeOut( 'fast' );\n\t }\n\t } );\n\t },\n\t\n\t // value\n\t value : function ( new_val ) {\n\t if ( new_val !== undefined && new_val !== null ) {\n\t // update color value\n\t this.$value.css( 'background-color', new_val );\n\t \n\t // check selected color in panel\n\t this.$( '.ui-color-picker-box' ).empty();\n\t this.$( this._getValue() ).html( this._templateCheck() );\n\t\n\t // trigger custom event\n\t this.options.onchange && this.options.onchange( new_val );\n\t }\n\t\n\t // return current value\n\t return this._getValue();\n\t },\n\t\n\t // get value from dom\n\t _getValue: function() {\n\t var rgb = this.$value.css( 'background-color' );\n\t rgb = rgb.match(/^rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)$/);\n\t if ( rgb ) {\n\t function hex( x ) {\n\t return ( '0' + parseInt( x ).toString( 16 ) ).slice( -2 );\n\t }\n\t return '#' + hex( rgb[ 1] ) + hex( rgb[ 2 ] ) + hex( rgb[ 3 ] );\n\t } else {\n\t return null;\n\t }\n\t },\n\t\n\t // build panel\n\t _build: function() {\n\t var $content = this._content({\n\t label : 'Theme Colors',\n\t colors : this.colors.base,\n\t padding : 10\n\t });\n\t for ( var i in this.colors.theme ) {\n\t var line_def = {};\n\t if ( i == 0 ) {\n\t line_def[ 'bottom' ] = true;\n\t } else {\n\t if ( i != this.colors.theme.length - 1 ) {\n\t line_def[ 'top' ] = true;\n\t line_def[ 'bottom' ] = true;\n\t } else {\n\t line_def[ 'top' ] = true;\n\t line_def[ 'padding' ] = 5;\n\t }\n\t }\n\t line_def[ 'colors' ] = this.colors.theme[ i ];\n\t this._content( line_def );\n\t }\n\t this._content({\n\t label : 'Standard Colors',\n\t colors : this.colors.standard,\n\t padding : 5\n\t });\n\t },\n\t\n\t // create content\n\t _content: function( options ) {\n\t // get parameters\n\t var label = options.label;\n\t var colors = options.colors;\n\t var padding = options.padding;\n\t var top = options.top;\n\t var bottom = options.bottom;\n\t\n\t // create lines\n\t var $content = $( this._templateContent() );\n\t\n\t // set label\n\t var $label = $content.find( '.label' );\n\t if ( options.label ) {\n\t $label.html( options.label );\n\t } else {\n\t $label.hide();\n\t }\n\t\n\t // build line\n\t var $line = $content.find( '.line' );\n\t this.$panel.append( $content );\n\t for ( var i in colors ) {\n\t var $box = $( this._templateBox( colors[ i ] ) );\n\t if ( top ) {\n\t $box.css( 'border-top', 'none' );\n\t $box.css( 'border-top-left-radius', '0px' );\n\t $box.css( 'border-top-right-radius', '0px' );\n\t }\n\t if ( bottom ) {\n\t $box.css( 'border-bottom', 'none' );\n\t $box.css( 'border-bottom-left-radius', '0px' );\n\t $box.css( 'border-bottom-right-radius', '0px' );\n\t }\n\t $line.append( $box );\n\t }\n\t if (padding) {\n\t $line.css( 'padding-bottom', padding );\n\t }\n\t return $content;\n\t },\n\t\n\t // check icon\n\t _templateCheck: function() {\n\t return '';\n\t },\n\t\n\t // content template\n\t _templateContent: function() {\n\t return '
';\n\t }\n\t });\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1)))\n\n/***/ },\n/* 48 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function($, _) {/** This class creates/wraps a drill down element. */\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4), __webpack_require__(21) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils, Options ) {\n\t\n\tvar View = Options.BaseIcons.extend({\n\t initialize: function( options ) {\n\t options.type = options.display || 'checkbox';\n\t options.multiple = ( options.type == 'checkbox' );\n\t Options.BaseIcons.prototype.initialize.call( this, options );\n\t },\n\t\n\t /** Set states for selected values */\n\t _setValue: function ( new_value ) {\n\t Options.BaseIcons.prototype._setValue.call( this, new_value );\n\t if ( new_value !== undefined && new_value !== null && this.header_index ) {\n\t var self = this;\n\t var values = $.isArray( new_value ) ? new_value : [ new_value ];\n\t _.each( values, function( v ) {\n\t var list = self.header_index[ v ];\n\t _.each( list, function( element ) {\n\t self._setState( element, true );\n\t });\n\t });\n\t }\n\t },\n\t\n\t /** Expand/collapse a sub group */\n\t _setState: function ( header_id, is_expanded ) {\n\t var $button = this.$( '.button-' + header_id );\n\t var $subgroup = this.$( '.subgroup-' + header_id );\n\t $button.data( 'is_expanded', is_expanded );\n\t if ( is_expanded ) {\n\t $subgroup.show();\n\t $button.removeClass( 'fa-plus-square' ).addClass( 'fa-minus-square' );\n\t } else {\n\t $subgroup.hide();\n\t $button.removeClass( 'fa-minus-square' ).addClass( 'fa-plus-square' );\n\t }\n\t },\n\t\n\t /** Template to create options tree */\n\t _templateOptions: function() {\n\t var self = this;\n\t this.header_index = {};\n\t\n\t // attach event handler\n\t function attach( $el, header_id ) {\n\t var $button = $el.find( '.button-' + header_id );\n\t $button.on( 'click', function() {\n\t self._setState( header_id, !$button.data( 'is_expanded' ) );\n\t });\n\t }\n\t\n\t // recursive function which iterates through options\n\t function iterate ( $tmpl, options, header ) {\n\t header = header || [];\n\t for ( i in options ) {\n\t var level = options[ i ];\n\t var has_options = level.options && level.options.length > 0;\n\t var new_header = header.slice( 0 );\n\t self.header_index[ level.value ] = new_header.slice( 0 );\n\t var $group = $( '' );\n\t if ( has_options ) {\n\t var header_id = Utils.uid();\n\t var $button = $( '' ).addClass( 'button-' + header_id ).addClass( 'ui-drilldown-button fa fa-plus-square' );\n\t var $subgroup = $( '' ).addClass( 'subgroup-' + header_id ).addClass( 'ui-drilldown-subgroup' );\n\t $group.append( $( '' )\n\t .append( $button )\n\t .append( self._templateOption( { label: level.name, value: level.value } ) ) );\n\t new_header.push( header_id );\n\t iterate ( $subgroup, level.options, new_header );\n\t $group.append( $subgroup );\n\t attach( $group, header_id );\n\t } else {\n\t $group.append( self._templateOption( { label: level.name, value: level.value } ) );\n\t }\n\t $tmpl.append( $group );\n\t }\n\t }\n\t\n\t // iterate through options and create dom\n\t var $tmpl = $( '' );\n\t iterate( $tmpl, this.model.get( 'data' ) );\n\t return $tmpl;\n\t },\n\t\n\t /** Template for drill down view */\n\t _template: function() {\n\t return $( '' ).addClass( 'ui-options-list drilldown-container' ).attr( 'id', this.model.id );\n\t }\n\t});\n\t\n\treturn {\n\t View: View\n\t}\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__(1), __webpack_require__(2)))\n\n/***/ },\n/* 49 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $, _) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4), __webpack_require__(7), __webpack_require__(22) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils, Ui, Select ) {\n\t\n\t/** Batch mode variations */\n\tvar Batch = { DISABLED: 'disabled', ENABLED: 'enabled', LINKED: 'linked' };\n\t\n\t/** List of available content selectors options */\n\tvar Configurations = {\n\t data: [\n\t { src: 'hda', icon: 'fa-file-o', tooltip: 'Single dataset', multiple: false, batch: Batch.DISABLED },\n\t { src: 'hda', icon: 'fa-files-o', tooltip: 'Multiple datasets', multiple: true, batch: Batch.LINKED },\n\t { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.LINKED } ],\n\t data_multiple: [\n\t { src: 'hda', icon: 'fa-files-o', tooltip: 'Multiple datasets', multiple: true, batch: Batch.DISABLED },\n\t { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.DISABLED } ],\n\t data_collection: [\n\t { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.DISABLED } ],\n\t workflow_data: [\n\t { src: 'hda', icon: 'fa-file-o', tooltip: 'Single dataset', multiple: false, batch: Batch.DISABLED } ],\n\t workflow_data_multiple: [\n\t { src: 'hda', icon: 'fa-files-o', tooltip: 'Multiple datasets', multiple: true, batch: Batch.DISABLED } ],\n\t workflow_data_collection: [\n\t { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.DISABLED } ],\n\t module_data: [\n\t { src: 'hda', icon: 'fa-file-o', tooltip: 'Single dataset', multiple: false, batch: Batch.DISABLED },\n\t { src: 'hda', icon: 'fa-files-o', tooltip: 'Multiple datasets', multiple: true, batch: Batch.ENABLED } ],\n\t module_data_collection: [\n\t { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.DISABLED },\n\t { src: 'hdca', icon: 'fa-folder', tooltip: 'Multiple collections', multiple: true, batch: Batch.ENABLED } ]\n\t};\n\t\n\t/** View for hda and hdca content selector ui elements */\n\tvar View = Backbone.View.extend({\n\t initialize : function( options ) {\n\t var self = this;\n\t this.model = options && options.model || new Backbone.Model({\n\t src_labels: { 'hda' : 'dataset', 'hdca': 'dataset collection' }\n\t }).set( options );\n\t this.setElement( $( '' ).addClass( 'ui-select-content' ) );\n\t this.button_product = new Ui.RadioButton.View( {\n\t value : 'false',\n\t data : [ { icon: 'fa fa-chain', value: 'false',\n\t tooltip: 'Linked inputs will be run in matched order with other datasets e.g. use this for matching forward and reverse reads.' },\n\t { icon: 'fa fa-chain-broken', value: 'true',\n\t tooltip: 'Unlinked dataset inputs will be run against *all* other inputs.' } ] } );\n\t var $batch_div = $( '' ).addClass( 'ui-form-info' )\n\t .append( $( '' ).addClass( 'fa fa-sitemap' ) )\n\t .append( $( '' ).html( 'This is a batch mode input field. Separate jobs will be triggered for each dataset selection.' ) );\n\t this.$batch = {\n\t linked : $batch_div.clone(),\n\t enabled : $batch_div.clone().append( $( '' )\n\t .append( $( '' ).addClass( 'ui-form-title' ).html( 'Batch options:' ) )\n\t .append( this.button_product.$el ) )\n\t .append( $( '' ).css( 'clear', 'both' ) )\n\t };\n\t\n\t // track current history elements\n\t this.history = {};\n\t\n\t // add listeners\n\t this.listenTo( this.model, 'change:data', this._changeData, this );\n\t this.listenTo( this.model, 'change:wait', this._changeWait, this );\n\t this.listenTo( this.model, 'change:current', this._changeCurrent, this );\n\t this.listenTo( this.model, 'change:value', this._changeValue, this );\n\t this.listenTo( this.model, 'change:type change:optional change:multiple change:extensions', this._changeType, this );\n\t this.render();\n\t\n\t // add change event\n\t this.on( 'change', function() { options.onchange && options.onchange( self.value() ) } );\n\t },\n\t\n\t render: function() {\n\t this._changeType();\n\t this._changeValue();\n\t this._changeWait();\n\t },\n\t\n\t /** Indicate that select fields are being updated */\n\t wait: function() {\n\t this.model.set( 'wait', true );\n\t },\n\t\n\t /** Indicate that the options update has been completed */\n\t unwait: function() {\n\t this.model.set( 'wait', false );\n\t },\n\t\n\t /** Update data representing selectable options */\n\t update: function( options ) {\n\t this.model.set( 'data', options );\n\t },\n\t\n\t /** Return the currently selected dataset values */\n\t value: function ( new_value ) {\n\t new_value !== undefined && this.model.set( 'value', new_value );\n\t var current = this.model.get( 'current' );\n\t if ( this.config[ current ] ) {\n\t var id_list = this.fields[ current ].value();\n\t if (id_list !== null) {\n\t id_list = $.isArray( id_list ) ? id_list : [ id_list ];\n\t if ( id_list.length > 0 ) {\n\t var result = this._batch( { values: [] } );\n\t for ( var i in id_list ) {\n\t var details = this.history[ id_list[ i ] + '_' + this.config[ current ].src ];\n\t if ( details ) {\n\t result.values.push( details );\n\t } else {\n\t Galaxy.emit.debug( 'ui-select-content::value()', 'Requested details not found for \\'' + id_list[ i ] + '\\'.' );\n\t return null;\n\t }\n\t }\n\t result.values.sort( function( a, b ) { return a.hid - b.hid } );\n\t return result;\n\t }\n\t }\n\t } else {\n\t Galaxy.emit.debug( 'ui-select-content::value()', 'Invalid value/source \\'' + new_value + '\\'.' );\n\t }\n\t return null;\n\t },\n\t\n\t /** Change of current select field */\n\t _changeCurrent: function() {\n\t var self = this;\n\t _.each( this.fields, function( field, i ) {\n\t if ( self.model.get( 'current' ) == i ) {\n\t field.$el.show();\n\t _.each( self.$batch, function( $batchfield, batchmode ) {\n\t $batchfield[ self.config[ i ].batch == batchmode ? 'show' : 'hide' ]();\n\t });\n\t self.button_type.value( i );\n\t } else {\n\t field.$el.hide();\n\t }\n\t });\n\t },\n\t\n\t /** Change of type */\n\t _changeType: function() {\n\t var self = this;\n\t\n\t // identify selector type identifier i.e. [ flavor ]_[ type ]_[ multiple ]\n\t var config_id = ( this.model.get( 'flavor' ) ? this.model.get( 'flavor' ) + '_' : '' ) +\n\t String( this.model.get( 'type' ) ) + ( this.model.get( 'multiple' ) ? '_multiple' : '' );\n\t if ( Configurations[ config_id ] ) {\n\t this.config = Configurations[ config_id ];\n\t } else {\n\t this.config = Configurations[ 'data' ];\n\t Galaxy.emit.debug( 'ui-select-content::_changeType()', 'Invalid configuration/type id \\'' + config_id + '\\'.' );\n\t }\n\t\n\t // prepare extension component of error message\n\t var extensions = Utils.textify( this.model.get( 'extensions' ) );\n\t var src_labels = this.model.get( 'src_labels' );\n\t\n\t // build views\n\t this.fields = [];\n\t this.button_data = [];\n\t _.each( this.config, function( c, i ) {\n\t self.button_data.push({\n\t value : i,\n\t icon : c.icon,\n\t tooltip : c.tooltip\n\t });\n\t self.fields.push(\n\t new Select.View({\n\t optional : self.model.get( 'optional' ),\n\t multiple : c.multiple,\n\t searchable : !c.multiple,\n\t error_text : 'No ' + ( extensions ? extensions + ' ' : '' ) + ( src_labels[ c.src ] || 'content' ) + ' available.',\n\t onchange : function() {\n\t self.trigger( 'change' );\n\t }\n\t })\n\t );\n\t });\n\t this.button_type = new Ui.RadioButton.View({\n\t value : this.model.get( 'current' ),\n\t data : this.button_data,\n\t onchange: function( value ) {\n\t self.model.set( 'current', value );\n\t self.trigger( 'change' );\n\t }\n\t });\n\t\n\t // append views\n\t this.$el.empty();\n\t var button_width = 0;\n\t if ( this.fields.length > 1 ) {\n\t this.$el.append( this.button_type.$el );\n\t button_width = Math.max( 0, this.fields.length * 35 ) + 'px';\n\t }\n\t _.each( this.fields, function( field ) {\n\t self.$el.append( field.$el.css( { 'margin-left': button_width } ) );\n\t });\n\t _.each( this.$batch, function( $batchfield, batchmode ) {\n\t self.$el.append( $batchfield.css( { 'margin-left': button_width } ) );\n\t });\n\t this.model.set( 'current', 0 );\n\t this._changeCurrent();\n\t this._changeData();\n\t },\n\t\n\t /** Change of wait flag */\n\t _changeWait: function() {\n\t var self = this;\n\t _.each( this.fields, function( field ) { field[ self.model.get( 'wait' ) ? 'wait' : 'unwait' ]() } );\n\t },\n\t\n\t /** Change of available options */\n\t _changeData: function() {\n\t var options = this.model.get( 'data' );\n\t var self = this;\n\t var select_options = {};\n\t _.each( options, function( items, src ) {\n\t select_options[ src ] = [];\n\t _.each( items, function( item ) {\n\t select_options[ src ].push({\n\t hid : item.hid,\n\t label: item.hid + ': ' + item.name,\n\t value: item.id\n\t });\n\t self.history[ item.id + '_' + src ] = item;\n\t });\n\t });\n\t _.each( this.config, function( c, i ) {\n\t select_options[ c.src ] && self.fields[ i ].add( select_options[ c.src ], function( a, b ) { return b.hid - a.hid } );\n\t });\n\t },\n\t\n\t /** Change of incoming value */\n\t _changeValue: function () {\n\t var new_value = this.model.get( 'value' );\n\t if ( new_value && new_value.values && new_value.values.length > 0 ) {\n\t // create list with content ids\n\t var list = [];\n\t _.each( new_value.values, function( value ) {\n\t list.push( value.id );\n\t });\n\t // sniff first suitable field type from config list\n\t var src = new_value.values[ 0 ].src;\n\t var multiple = new_value.values.length > 1;\n\t for( var i = 0; i < this.config.length; i++ ) {\n\t var field = this.fields[ i ];\n\t var c = this.config[ i ];\n\t if ( c.src == src && [ multiple, true ].indexOf( c.multiple ) !== -1 ) {\n\t this.model.set( 'current', i );\n\t field.value( list );\n\t break;\n\t }\n\t }\n\t } else {\n\t _.each( this.fields, function( field ) {\n\t field.value( null );\n\t });\n\t }\n\t },\n\t\n\t /** Assists in identifying the batch mode */\n\t _batch: function( result ) {\n\t result[ 'batch' ] = false;\n\t var current = this.model.get( 'current' );\n\t var config = this.config[ current ];\n\t if ( config.src == 'hdca' && !config.multiple ) {\n\t var hdca = this.history[ this.fields[ current ].value() + '_hdca' ];\n\t if ( hdca && hdca.map_over_type ) {\n\t result[ 'batch' ] = true;\n\t }\n\t }\n\t if ( config.batch == Batch.LINKED || config.batch == Batch.ENABLED ) {\n\t result[ 'batch' ] = true;\n\t if ( config.batch == Batch.ENABLED && this.button_product.value() === 'true' ) {\n\t result[ 'product' ] = true;\n\t }\n\t }\n\t return result;\n\t }\n\t});\n\t\n\treturn {\n\t View: View\n\t}\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 50 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone) {// dependencies\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4), __webpack_require__(20)], __WEBPACK_AMD_DEFINE_RESULT__ = function(Utils, List) {\n\t\n\t/**\n\t * FTP file selector\n\t */\n\tvar View = Backbone.View.extend({\n\t // initialize\n\t initialize : function(options) {\n\t // link this\n\t var self = this;\n\t\n\t // create ui-list view to keep track of selected ftp files\n\t this.ftpfile_list = new List.View({\n\t name : 'file',\n\t optional : options.optional,\n\t multiple : options.multiple,\n\t onchange : function() {\n\t options.onchange && options.onchange(self.value());\n\t }\n\t });\n\t\n\t // create elements\n\t this.setElement(this.ftpfile_list.$el);\n\t\n\t // initial fetch of ftps\n\t Utils.get({\n\t url : Galaxy.root + 'api/remote_files',\n\t success : function(response) {\n\t var data = [];\n\t for (var i in response) {\n\t data.push({\n\t value : response[i]['path'],\n\t label : response[i]['path']\n\t });\n\t }\n\t self.ftpfile_list.update(data);\n\t }\n\t });\n\t },\n\t\n\t /** Return/Set currently selected ftp datasets */\n\t value: function(val) {\n\t return this.ftpfile_list.value(val);\n\t }\n\t});\n\t\n\treturn {\n\t View: View\n\t}\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3)))\n\n/***/ },\n/* 51 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone) {// dependencies\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4), __webpack_require__(7), __webpack_require__(14), __webpack_require__(20)], __WEBPACK_AMD_DEFINE_RESULT__ = function(Utils, Ui, Table, List) {\n\t\n\t// collection of libraries\n\tvar Libraries = Backbone.Collection.extend({\n\t url: Galaxy.root + 'api/libraries?deleted=false'\n\t});\n\t\n\t// collection of dataset\n\tvar LibraryDatasets = Backbone.Collection.extend({\n\t initialize: function() {\n\t var self = this;\n\t this.config = new Backbone.Model({ library_id: null });\n\t this.config.on('change', function() {\n\t self.fetch({ reset: true });\n\t });\n\t },\n\t url: function() {\n\t return Galaxy.root + 'api/libraries/' + this.config.get('library_id') + '/contents';\n\t }\n\t});\n\t\n\t// hda/hdca content selector ui element\n\tvar View = Backbone.View.extend({\n\t // initialize\n\t initialize : function(options) {\n\t // link this\n\t var self = this;\n\t\n\t // collections\n\t this.libraries = new Libraries();\n\t this.datasets = new LibraryDatasets();\n\t\n\t // link app and options\n\t this.options = options;\n\t\n\t // select field for the library\n\t // TODO: Remove this once the library API supports searching for library datasets\n\t this.library_select = new Ui.Select.View({\n\t onchange : function(value) {\n\t self.datasets.config.set('library_id', value);\n\t }\n\t });\n\t\n\t // create ui-list view to keep track of selected data libraries\n\t this.dataset_list = new List.View({\n\t name : 'dataset',\n\t optional : options.optional,\n\t multiple : options.multiple,\n\t onchange : function() {\n\t self.trigger('change');\n\t }\n\t });\n\t\n\t // add reset handler for fetched libraries\n\t this.libraries.on('reset', function() {\n\t var data = [];\n\t self.libraries.each(function(model) {\n\t data.push({\n\t value : model.id,\n\t label : model.get('name')\n\t });\n\t });\n\t self.library_select.update(data);\n\t });\n\t\n\t // add reset handler for fetched library datasets\n\t this.datasets.on('reset', function() {\n\t var data = [];\n\t var library_current = self.library_select.text();\n\t if (library_current !== null) {\n\t self.datasets.each(function(model) {\n\t if (model.get('type') === 'file') {\n\t data.push({\n\t value : model.id,\n\t label : model.get('name')\n\t });\n\t }\n\t });\n\t }\n\t self.dataset_list.update(data);\n\t });\n\t\n\t // add change event. fires on trigger\n\t this.on('change', function() {\n\t options.onchange && options.onchange(self.value());\n\t });\n\t\n\t // create elements\n\t this.setElement(this._template());\n\t this.$('.library-select').append(this.library_select.$el);\n\t this.$el.append(this.dataset_list.$el);\n\t\n\t // initial fetch of libraries\n\t this.libraries.fetch({\n\t reset: true,\n\t success: function() {\n\t self.library_select.trigger('change');\n\t if (self.options.value !== undefined) {\n\t self.value(self.options.value);\n\t }\n\t }\n\t });\n\t },\n\t\n\t /** Return/Set currently selected library datasets */\n\t value: function(val) {\n\t return this.dataset_list.value(val);\n\t },\n\t\n\t /** Template */\n\t _template: function() {\n\t return '
' +\n\t '
' +\n\t 'Select Library' +\n\t '' +\n\t '
' +\n\t '
';\n\t }\n\t});\n\t\n\treturn {\n\t View: View\n\t}\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3)))\n\n/***/ },\n/* 52 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils ) {\n\tvar View = Backbone.View.extend({\n\t initialize : function( options ) {\n\t var self = this;\n\t this.options = Utils.merge( options, {\n\t id : Utils.uid(),\n\t min : null,\n\t max : null,\n\t step : null,\n\t precise : false,\n\t split : 10000\n\t } );\n\t\n\t // create new element\n\t this.setElement( this._template( this.options ) );\n\t\n\t // determine wether to use the slider\n\t this.useslider = this.options.max !== null && this.options.min !== null && this.options.max > this.options.min;\n\t\n\t // set default step size\n\t if ( this.options.step === null ) {\n\t this.options.step = 1.0;\n\t if ( this.options.precise && this.useslider ) {\n\t this.options.step = ( this.options.max - this.options.min ) / this.options.split;\n\t }\n\t }\n\t\n\t // create slider if min and max are defined properly\n\t if ( this.useslider ) {\n\t this.$slider = this.$( '#slider' );\n\t this.$slider.slider( this.options );\n\t this.$slider.on( 'slide', function ( event, ui ) {\n\t self.value( ui.value );\n\t });\n\t } else {\n\t this.$( '.ui-form-slider-text' ).css( 'width', '100%' );\n\t }\n\t\n\t // link text input field\n\t this.$text = this.$( '#text' );\n\t\n\t // set initial value\n\t this.options.value !== undefined && ( this.value( this.options.value ) );\n\t\n\t // add text field event\n\t var pressed = [];\n\t this.$text.on( 'change', function () {\n\t self.value( $( this ).val() );\n\t });\n\t this.$text.on( 'keyup', function( e ) {\n\t pressed[e.which] = false;\n\t self.options.onchange && self.options.onchange( $( this ).val() );\n\t });\n\t this.$text.on( 'keydown', function ( e ) {\n\t var v = e.which;\n\t pressed[ v ] = true;\n\t if ( self.options.is_workflow && pressed[ 16 ] && v == 52 ) {\n\t self.value( '$' )\n\t event.preventDefault();\n\t } else if (!( v == 8 || v == 9 || v == 13 || v == 37 || v == 39 || ( v >= 48 && v <= 57 && !pressed[ 16 ] ) || ( v >= 96 && v <= 105 )\n\t || ( ( v == 190 || v == 110 ) && $( this ).val().indexOf( '.' ) == -1 && self.options.precise )\n\t || ( ( v == 189 || v == 109 ) && $( this ).val().indexOf( '-' ) == -1 )\n\t || self._isParameter( $( this ).val() )\n\t || pressed[ 91 ] || pressed[ 17 ] ) ) {\n\t event.preventDefault();\n\t }\n\t });\n\t },\n\t\n\t /** Set and Return the current value\n\t */\n\t value : function ( new_val ) {\n\t if ( new_val !== undefined ) {\n\t if ( new_val !== null && new_val !== '' && !this._isParameter( new_val ) ) {\n\t isNaN( new_val ) && ( new_val = 0 );\n\t this.options.max !== null && ( new_val = Math.min( new_val, this.options.max ) );\n\t this.options.min !== null && ( new_val = Math.max( new_val, this.options.min ) );\n\t }\n\t this.$slider && this.$slider.slider( 'value', new_val );\n\t this.$text.val( new_val );\n\t this.options.onchange && this.options.onchange( new_val );\n\t }\n\t return this.$text.val();\n\t },\n\t\n\t /** Return true if the field contains a workflow parameter i.e. $('name')\n\t */\n\t _isParameter: function( value ) {\n\t return this.options.is_workflow && String( value ).substring( 0, 1 ) === '$';\n\t },\n\t\n\t /** Slider template\n\t */\n\t _template: function( options ) {\n\t return '
' +\n\t '' +\n\t '
' +\n\t '
';\n\t }\n\t});\n\t\n\treturn {\n\t View : View\n\t};\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1)))\n\n/***/ },\n/* 53 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone) {// dependencies\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function() {\n\t\n\t// model\n\tvar Model = Backbone.Model.extend({\n\t defaults: {\n\t extension : 'auto',\n\t genome : '?',\n\t url_paste : '',\n\t status : 'init',\n\t info : null,\n\t file_name : '',\n\t file_mode : '',\n\t file_size : 0,\n\t file_type : null,\n\t file_path : '',\n\t file_data : null,\n\t percentage : 0,\n\t space_to_tab : false,\n\t to_posix_lines : true,\n\t enabled : true\n\t },\n\t reset: function(attr) {\n\t this.clear().set(this.defaults).set(attr);\n\t }\n\t});\n\t\n\t// collection\n\tvar Collection = Backbone.Collection.extend({\n\t model: Model\n\t});\n\t\n\t// return\n\treturn {\n\t Model: Model,\n\t Collection : Collection\n\t};\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3)))\n\n/***/ },\n/* 54 */,\n/* 55 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $) {/**\n\t * This class defines a queue to ensure that multiple deferred callbacks are executed sequentially.\n\t */\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4)], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils ) {\n\treturn Backbone.Model.extend({\n\t initialize: function(){\n\t this.active = {};\n\t this.last = null;\n\t },\n\t\n\t /** Adds a callback to the queue. Upon execution a deferred object is parsed to the callback i.e. callback( deferred ).\n\t * If the callback does not take any arguments, the deferred is resolved instantly.\n\t */\n\t execute: function( callback ) {\n\t var self = this;\n\t var id = Utils.uid();\n\t var has_deferred = callback.length > 0;\n\t\n\t // register process\n\t this.active[ id ] = true;\n\t\n\t // deferred process\n\t var process = $.Deferred();\n\t process.promise().always(function() {\n\t delete self.active[ id ];\n\t has_deferred && Galaxy.emit.debug( 'deferred::execute()', this.state().charAt(0).toUpperCase() + this.state().slice(1) + ' ' + id );\n\t });\n\t\n\t // deferred queue\n\t $.when( this.last ).always(function() {\n\t if ( self.active[ id ] ) {\n\t has_deferred && Galaxy.emit.debug( 'deferred::execute()', 'Running ' + id );\n\t callback( process );\n\t !has_deferred && process.resolve();\n\t } else {\n\t process.reject();\n\t }\n\t });\n\t this.last = process.promise();\n\t },\n\t\n\t /** Resets the promise queue. All currently queued but unexecuted callbacks/promises will be rejected.\n\t */\n\t reset: function() {\n\t Galaxy.emit.debug('deferred::execute()', 'Reset');\n\t for ( var i in this.active ) {\n\t this.active[ i ] = false;\n\t }\n\t },\n\t\n\t /** Returns true if all processes are done.\n\t */\n\t ready: function() {\n\t return $.isEmptyObject( this.active );\n\t }\n\t});\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1)))\n\n/***/ },\n/* 56 */,\n/* 57 */,\n/* 58 */,\n/* 59 */,\n/* 60 */,\n/* 61 */,\n/* 62 */,\n/* 63 */,\n/* 64 */,\n/* 65 */,\n/* 66 */,\n/* 67 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(6),\n\t __webpack_require__(5),\n\t __webpack_require__(16),\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( baseMVC, _l ){\n\t// =============================================================================\n\t/** A view on any model that has a 'annotation' attribute\n\t */\n\tvar AnnotationEditor = Backbone.View\n\t .extend( baseMVC.LoggableMixin )\n\t .extend( baseMVC.HiddenUntilActivatedViewMixin ).extend({\n\t\n\t tagName : 'div',\n\t className : 'annotation-display',\n\t\n\t /** Set up listeners, parse options */\n\t initialize : function( options ){\n\t options = options || {};\n\t this.tooltipConfig = options.tooltipConfig || { placement: 'bottom' };\n\t //console.debug( this, options );\n\t // only listen to the model only for changes to annotations\n\t this.listenTo( this.model, 'change:annotation', function(){\n\t this.render();\n\t });\n\t this.hiddenUntilActivated( options.$activator, options );\n\t },\n\t\n\t /** Build the DOM elements, call select to on the created input, and set up behaviors */\n\t render : function(){\n\t var view = this;\n\t this.$el.html( this._template() );\n\t this.$el.find( \"[title]\" ).tooltip( this.tooltipConfig );\n\t\n\t //TODO: handle empties better\n\t this.$annotation().make_text_editable({\n\t use_textarea: true,\n\t on_finish: function( newAnnotation ){\n\t view.$annotation().text( newAnnotation );\n\t view.model.save({ annotation: newAnnotation }, { silent: true })\n\t .fail( function(){\n\t view.$annotation().text( view.model.previous( 'annotation' ) );\n\t });\n\t }\n\t });\n\t return this;\n\t },\n\t\n\t /** @returns {String} the html text used to build the view's DOM */\n\t _template : function(){\n\t var annotation = this.model.get( 'annotation' );\n\t //if( !annotation ){\n\t // //annotation = [ '', _l( 'Click to add an annotation' ), '' ].join( '' );\n\t // annotation = [ '' ].join( '' );\n\t //}\n\t return [\n\t //TODO: make prompt optional\n\t '',\n\t // set up initial tags by adding as CSV to input vals (necc. to init select2)\n\t '
',\n\t _.escape( annotation ),\n\t '
'\n\t ].join( '' );\n\t },\n\t\n\t /** @returns {jQuery} the main element for this view */\n\t $annotation : function(){\n\t return this.$el.find( '.annotation' );\n\t },\n\t\n\t /** shut down event listeners and remove this view's DOM */\n\t remove : function(){\n\t this.$annotation.off();\n\t this.stopListening( this.model );\n\t Backbone.View.prototype.remove.call( this );\n\t },\n\t\n\t /** string rep */\n\t toString : function(){ return [ 'AnnotationEditor(', this.model + '', ')' ].join(''); }\n\t});\n\t// =============================================================================\n\treturn {\n\t AnnotationEditor : AnnotationEditor\n\t};\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2)))\n\n/***/ },\n/* 68 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(75),\n\t __webpack_require__(30),\n\t __webpack_require__(29),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( LIST_VIEW, DC_MODEL, DC_LI, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'collections';\n\t/* =============================================================================\n\tTODO:\n\t\n\t============================================================================= */\n\t/** @class non-editable, read-only View/Controller for a dataset collection.\n\t */\n\tvar _super = LIST_VIEW.ModelListPanel;\n\tvar CollectionView = _super.extend(\n\t/** @lends CollectionView.prototype */{\n\t //MODEL is either a DatasetCollection (or subclass) or a DatasetCollectionElement (list of pairs)\n\t _logNamespace : logNamespace,\n\t\n\t className : _super.prototype.className + ' dataset-collection-panel',\n\t\n\t /** sub view class used for datasets */\n\t DatasetDCEViewClass : DC_LI.DatasetDCEListItemView,\n\t /** sub view class used for nested collections */\n\t NestedDCDCEViewClass: DC_LI.NestedDCDCEListItemView,\n\t /** key of attribute in model to assign to this.collection */\n\t modelCollectionKey : 'elements',\n\t\n\t // ......................................................................... SET UP\n\t /** Set up the view, set up storage, bind listeners to HistoryContents events\n\t * @param {Object} attributes optional settings for the panel\n\t */\n\t initialize : function( attributes ){\n\t _super.prototype.initialize.call( this, attributes );\n\t this.linkTarget = attributes.linkTarget || '_blank';\n\t\n\t this.hasUser = attributes.hasUser;\n\t /** A stack of panels that currently cover or hide this panel */\n\t this.panelStack = [];\n\t /** The text of the link to go back to the panel containing this one */\n\t this.parentName = attributes.parentName;\n\t /** foldout or drilldown */\n\t this.foldoutStyle = attributes.foldoutStyle || 'foldout';\n\t },\n\t\n\t // ------------------------------------------------------------------------ sub-views\n\t /** In this override, use model.getVisibleContents */\n\t _filterCollection : function(){\n\t//TODO: should *not* be model.getVisibleContents - visibility is not model related\n\t return this.model.getVisibleContents();\n\t },\n\t\n\t /** override to return proper view class based on element_type */\n\t _getItemViewClass : function( model ){\n\t //this.debug( this + '._getItemViewClass:', model );\n\t//TODO: subclasses use DCEViewClass - but are currently unused - decide\n\t switch( model.get( 'element_type' ) ){\n\t case 'hda':\n\t return this.DatasetDCEViewClass;\n\t case 'dataset_collection':\n\t return this.NestedDCDCEViewClass;\n\t }\n\t throw new TypeError( 'Unknown element type:', model.get( 'element_type' ) );\n\t },\n\t\n\t /** override to add link target and anon */\n\t _getItemViewOptions : function( model ){\n\t var options = _super.prototype._getItemViewOptions.call( this, model );\n\t return _.extend( options, {\n\t linkTarget : this.linkTarget,\n\t hasUser : this.hasUser,\n\t//TODO: could move to only nested: list:paired\n\t foldoutStyle : this.foldoutStyle\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ collection sub-views\n\t /** In this override, add/remove expanded/collapsed model ids to/from web storage */\n\t _setUpItemViewListeners : function( view ){\n\t var panel = this;\n\t _super.prototype._setUpItemViewListeners.call( panel, view );\n\t\n\t // use pub-sub to: handle drilldown expansion and collapse\n\t panel.listenTo( view, {\n\t 'expanded:drilldown': function( v, drilldown ){\n\t this._expandDrilldownPanel( drilldown );\n\t },\n\t 'collapsed:drilldown': function( v, drilldown ){\n\t this._collapseDrilldownPanel( drilldown );\n\t }\n\t });\n\t return this;\n\t },\n\t\n\t /** Handle drill down by hiding this panels list and controls and showing the sub-panel */\n\t _expandDrilldownPanel : function( drilldown ){\n\t this.panelStack.push( drilldown );\n\t // hide this panel's controls and list, set the name for back navigation, and attach to the $el\n\t this.$( '> .controls' ).add( this.$list() ).hide();\n\t drilldown.parentName = this.model.get( 'name' );\n\t this.$el.append( drilldown.render().$el );\n\t },\n\t\n\t /** Handle drilldown close by freeing the panel and re-rendering this panel */\n\t _collapseDrilldownPanel : function( drilldown ){\n\t this.panelStack.pop();\n\t this.render();\n\t },\n\t\n\t // ------------------------------------------------------------------------ panel events\n\t /** event map */\n\t events : {\n\t 'click .navigation .back' : 'close'\n\t },\n\t\n\t /** close/remove this collection panel */\n\t close : function( event ){\n\t this.$el.remove();\n\t this.trigger( 'close' );\n\t },\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'CollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t//------------------------------------------------------------------------------ TEMPLATES\n\tCollectionView.prototype.templates = (function(){\n\t\n\t var controlsTemplate = BASE_MVC.wrapTemplate([\n\t '
',\n\t '<% } %>'\n\t ], 'dataset' ),\n\t\n\t deleted : BASE_MVC.wrapTemplate([\n\t // in this override, provide links to undelete or purge the dataset\n\t '<% if( dataset.deleted && !dataset.purged ){ %>',\n\t // deleted not purged\n\t '
',\n\t '<% } %>'\n\t ], 'visualizations' );\n\t\n\t return _.extend( {}, _super.prototype.templates, {\n\t warnings : warnings,\n\t visualizations : visualizationsTemplate\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t return {\n\t DatasetListItemEdit : DatasetListItemEdit\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__(1)))\n\n/***/ },\n/* 70 */\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__(10),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( STATES, BASE_MVC, _l ){\n\t'use strict';\n\t\n\tvar logNamespace = 'dataset';\n\t//==============================================================================\n\tvar searchableMixin = BASE_MVC.SearchableModelMixin;\n\t/** @class base model for any DatasetAssociation (HDAs, LDDAs, DatasetCollectionDAs).\n\t * No knowledge of what type (HDA/LDDA/DCDA) should be needed here.\n\t * The DA's are made searchable (by attribute) by mixing in SearchableModelMixin.\n\t */\n\tvar DatasetAssociation = Backbone.Model\n\t .extend( BASE_MVC.LoggableMixin )\n\t .extend( BASE_MVC.mixin( searchableMixin, /** @lends DatasetAssociation.prototype */{\n\t _logNamespace : logNamespace,\n\t\n\t /** default attributes for a model */\n\t defaults : {\n\t state : STATES.NEW,\n\t deleted : false,\n\t purged : false,\n\t\n\t // unreliable attribute\n\t name : '(unnamed dataset)',\n\t\n\t//TODO: update to false when this is correctly passed from the API (when we have a security model for this)\n\t accessible : true,\n\t\n\t // sniffed datatype (sam, tabular, bed, etc.)\n\t data_type : '',\n\t file_ext : '',\n\t\n\t // size in bytes\n\t file_size : 0,\n\t\n\t // array of associated file types (eg. [ 'bam_index', ... ])\n\t meta_files : [],\n\t\n\t misc_blurb : '',\n\t misc_info : '',\n\t\n\t tags : []\n\t // do NOT default on annotation, as this default is valid and will be passed on 'save'\n\t // which is incorrect behavior when the model is only partially fetched (annos are not passed in summary data)\n\t //annotation : ''\n\t },\n\t\n\t /** instance vars and listeners */\n\t initialize : function( attributes, options ){\n\t this.debug( this + '(Dataset).initialize', attributes, options );\n\t\n\t //!! this state is not in trans.app.model.Dataset.states - set it here -\n\t if( !this.get( 'accessible' ) ){\n\t this.set( 'state', STATES.NOT_VIEWABLE );\n\t }\n\t\n\t /** Datasets rely/use some web controllers - have the model generate those URLs on startup */\n\t this.urls = this._generateUrls();\n\t\n\t this._setUpListeners();\n\t },\n\t\n\t /** returns misc. web urls for rendering things like re-run, display, etc. */\n\t _generateUrls : function(){\n\t//TODO: would be nice if the API did this\n\t var id = this.get( 'id' );\n\t if( !id ){ return {}; }\n\t var urls = {\n\t 'purge' : 'datasets/' + id + '/purge_async',\n\t 'display' : 'datasets/' + id + '/display/?preview=True',\n\t 'edit' : 'datasets/' + id + '/edit',\n\t 'download' : 'datasets/' + id + '/display?to_ext=' + this.get( 'file_ext' ),\n\t 'report_error' : 'dataset/errors?id=' + id,\n\t 'rerun' : 'tool_runner/rerun?id=' + id,\n\t 'show_params' : 'datasets/' + id + '/show_params',\n\t 'visualization' : 'visualization',\n\t 'meta_download' : 'dataset/get_metadata_file?hda_id=' + id + '&metadata_name='\n\t };\n\t//TODO: global\n\t _.each( urls, function( value, key ){\n\t urls[ key ] = Galaxy.root + value;\n\t });\n\t this.urls = urls;\n\t return urls;\n\t },\n\t\n\t /** set up any event listeners\n\t * event: state:ready fired when this DA moves into/is already in a ready state\n\t */\n\t _setUpListeners : function(){\n\t // if the state has changed and the new state is a ready state, fire an event\n\t this.on( 'change:state', function( currModel, newState ){\n\t this.log( this + ' has changed state:', currModel, newState );\n\t if( this.inReadyState() ){\n\t this.trigger( 'state:ready', currModel, newState, this.previous( 'state' ) );\n\t }\n\t });\n\t // the download url (currently) relies on having a correct file extension\n\t this.on( 'change:id change:file_ext', function( currModel ){\n\t this._generateUrls();\n\t });\n\t },\n\t\n\t // ........................................................................ common queries\n\t /** override to add urls */\n\t toJSON : function(){\n\t var json = Backbone.Model.prototype.toJSON.call( this );\n\t //console.warn( 'returning json?' );\n\t //return json;\n\t return _.extend( json, {\n\t urls : this.urls\n\t });\n\t },\n\t\n\t /** Is this dataset deleted or purged? */\n\t isDeletedOrPurged : function(){\n\t return ( this.get( 'deleted' ) || this.get( 'purged' ) );\n\t },\n\t\n\t /** Is this dataset in a 'ready' state; where 'Ready' states are states where no\n\t * processing (for the ds) is left to do on the server.\n\t */\n\t inReadyState : function(){\n\t var ready = _.contains( STATES.READY_STATES, this.get( 'state' ) );\n\t return ( this.isDeletedOrPurged() || ready );\n\t },\n\t\n\t /** Does this model already contain detailed data (as opposed to just summary level data)? */\n\t hasDetails : function(){\n\t // if it's inaccessible assume it has everything it needs\n\t if( !this.get( 'accessible' ) ){ return true; }\n\t //?? this may not be reliable\n\t return _.has( this.attributes, 'genome_build' );\n\t },\n\t\n\t /** Convenience function to match dataset.has_data. */\n\t hasData : function(){\n\t return ( this.get( 'file_size' ) > 0 );\n\t },\n\t\n\t // ........................................................................ ajax\n\t fetch : function( options ){\n\t var dataset = this;\n\t return Backbone.Model.prototype.fetch.call( this, options )\n\t .always( function(){\n\t dataset._generateUrls();\n\t });\n\t },\n\t\n\t //NOTE: subclasses of DA's will need to implement url and urlRoot in order to have these work properly\n\t /** save this dataset, _Mark_ing it as deleted (just a flag) */\n\t 'delete' : function( options ){\n\t if( this.get( 'deleted' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: true }, options );\n\t },\n\t /** save this dataset, _Mark_ing it as undeleted */\n\t undelete : function( options ){\n\t if( !this.get( 'deleted' ) || this.get( 'purged' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: false }, options );\n\t },\n\t\n\t /** remove the file behind this dataset from the filesystem (if permitted) */\n\t purge : function _purge( options ){\n\t//TODO: use, override model.destroy, HDA.delete({ purge: true })\n\t if( this.get( 'purged' ) ){ return jQuery.when(); }\n\t options = options || {};\n\t //var hda = this,\n\t // //xhr = jQuery.ajax( this.url() + '?' + jQuery.param({ purge: true }), _.extend({\n\t // xhr = jQuery.ajax( this.url(), _.extend({\n\t // type : 'DELETE',\n\t // data : {\n\t // purge : true\n\t // }\n\t // }, options ));\n\t //\n\t //xhr.done( function( response ){\n\t // hda.debug( 'response', response );\n\t // //hda.set({ deleted: true, purged: true });\n\t // hda.set( response );\n\t //});\n\t //return xhr;\n\t\n\t options.url = this.urls.purge;\n\t\n\t //TODO: ideally this would be a DELETE call to the api\n\t // using purge async for now\n\t var hda = this,\n\t xhr = jQuery.ajax( options );\n\t xhr.done( function( message, status, responseObj ){\n\t hda.set({ deleted: true, purged: true });\n\t });\n\t xhr.fail( function( xhr, status, message ){\n\t // Exception messages are hidden within error page including: '...not allowed in this Galaxy instance.'\n\t // unbury and re-add to xhr\n\t var error = _l( \"Unable to purge dataset\" );\n\t var messageBuriedInUnfortunatelyFormattedError = ( 'Removal of datasets by users '\n\t + 'is not allowed in this Galaxy instance' );\n\t if( xhr.responseJSON && xhr.responseJSON.error ){\n\t error = xhr.responseJSON.error;\n\t } else if( xhr.responseText.indexOf( messageBuriedInUnfortunatelyFormattedError ) !== -1 ){\n\t error = messageBuriedInUnfortunatelyFormattedError;\n\t }\n\t xhr.responseText = error;\n\t hda.trigger( 'error', hda, xhr, options, _l( error ), { error: error } );\n\t });\n\t return xhr;\n\t },\n\t\n\t // ........................................................................ searching\n\t // see base-mvc, SearchableModelMixin\n\t\n\t /** what attributes of an HDA will be used in a text search */\n\t searchAttributes : [\n\t 'name', 'file_ext', 'genome_build', 'misc_blurb', 'misc_info', 'annotation', 'tags'\n\t ],\n\t\n\t /** our attr keys don't often match the labels we display to the user - so, when using\n\t * attribute specifiers ('name=\"bler\"') in a term, allow passing in aliases for the\n\t * following attr keys.\n\t */\n\t searchAliases : {\n\t title : 'name',\n\t format : 'file_ext',\n\t database : 'genome_build',\n\t blurb : 'misc_blurb',\n\t description : 'misc_blurb',\n\t info : 'misc_info',\n\t tag : 'tags'\n\t },\n\t\n\t // ........................................................................ misc\n\t /** String representation */\n\t toString : function(){\n\t var nameAndId = this.get( 'id' ) || '';\n\t if( this.get( 'name' ) ){\n\t nameAndId = '\"' + this.get( 'name' ) + '\",' + nameAndId;\n\t }\n\t return 'Dataset(' + nameAndId + ')';\n\t }\n\t}));\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone collection for dataset associations.\n\t */\n\tvar DatasetAssociationCollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend(\n\t/** @lends HistoryContents.prototype */{\n\t _logNamespace : logNamespace,\n\t\n\t model : DatasetAssociation,\n\t\n\t /** root api url */\n\t urlRoot : Galaxy.root + 'api/datasets',\n\t\n\t /** url fn */\n\t url : function(){\n\t return this.urlRoot;\n\t },\n\t\n\t // ........................................................................ common queries\n\t /** Get the ids of every item in this collection\n\t * @returns array of encoded ids\n\t */\n\t ids : function(){\n\t return this.map( function( item ){ return item.get('id'); });\n\t },\n\t\n\t /** Get contents that are not ready\n\t * @returns array of content models\n\t */\n\t notReady : function(){\n\t return this.filter( function( content ){\n\t return !content.inReadyState();\n\t });\n\t },\n\t\n\t /** return true if any datasets don't have details */\n\t haveDetails : function(){\n\t return this.all( function( dataset ){ return dataset.hasDetails(); });\n\t },\n\t\n\t // ........................................................................ ajax\n\t /** using a queue, perform ajaxFn on each of the models in this collection */\n\t ajaxQueue : function( ajaxFn, options ){\n\t var deferred = jQuery.Deferred(),\n\t startingLength = this.length,\n\t responses = [];\n\t\n\t if( !startingLength ){\n\t deferred.resolve([]);\n\t return deferred;\n\t }\n\t\n\t // use reverse order (stylistic choice)\n\t var ajaxFns = this.chain().reverse().map( function( dataset, i ){\n\t return function(){\n\t var xhr = ajaxFn.call( dataset, options );\n\t // if successful, notify using the deferred to allow tracking progress\n\t xhr.done( function( response ){\n\t deferred.notify({ curr: i, total: startingLength, response: response, model: dataset });\n\t });\n\t // (regardless of previous error or success) if not last ajax call, shift and call the next\n\t // if last fn, resolve deferred\n\t xhr.always( function( response ){\n\t responses.push( response );\n\t if( ajaxFns.length ){\n\t ajaxFns.shift()();\n\t } else {\n\t deferred.resolve( responses );\n\t }\n\t });\n\t };\n\t }).value();\n\t // start the queue\n\t ajaxFns.shift()();\n\t\n\t return deferred;\n\t },\n\t\n\t // ........................................................................ sorting/filtering\n\t /** return a new collection of datasets whose attributes contain the substring matchesWhat */\n\t matches : function( matchesWhat ){\n\t return this.filter( function( dataset ){\n\t return dataset.matches( matchesWhat );\n\t });\n\t },\n\t\n\t // ........................................................................ misc\n\t ///** Convert this ad-hoc collection of hdas to a formal collection tracked\n\t // by the server.\n\t //**/\n\t //promoteToHistoryDatasetCollection : function _promote( history, collection_type, options ){\n\t //},\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'DatasetAssociationCollection(', this.length, ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t DatasetAssociation : DatasetAssociation,\n\t DatasetAssociationCollection : DatasetAssociationCollection\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 71 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(32),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( DATASET_LI, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\t//==============================================================================\n\tvar _super = DATASET_LI.DatasetListItemView;\n\t/** @class Read only view for HistoryDatasetAssociation.\n\t * Since there are no controls on the HDAView to hide the dataset,\n\t * the primary thing this class does (currently) is override templates\n\t * to render the HID.\n\t */\n\tvar HDAListItemView = _super.extend(\n\t/** @lends HDAListItemView.prototype */{\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t //logger : console,\n\t\n\t className : _super.prototype.className + \" history-content\",\n\t\n\t initialize : function( attributes, options ){\n\t _super.prototype.initialize.call( this, attributes, options );\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'HDAListItemView(' + modelString + ')';\n\t }\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tHDAListItemView.prototype.templates = (function(){\n\t//TODO: move to require text! plugin\n\t\n\t var titleBarTemplate = BASE_MVC.wrapTemplate([\n\t // adding the hid display to the title\n\t '
',\n\t '',\n\t '
',\n\t //TODO: remove whitespace and use margin-right\n\t '<%- dataset.hid %> ',\n\t '<%- dataset.name %>',\n\t '
',\n\t '
'\n\t ], 'dataset' );\n\t\n\t var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n\t hidden : BASE_MVC.wrapTemplate([\n\t // add a warning when hidden\n\t '<% if( !dataset.visible ){ %>',\n\t '
',\n\t _l( 'This dataset has been hidden' ),\n\t '
',\n\t '<% } %>'\n\t ], 'dataset' )\n\t });\n\t\n\t return _.extend( {}, _super.prototype.templates, {\n\t titleBar : titleBarTemplate,\n\t warnings : warnings\n\t });\n\t}());\n\t\n\t\n\t\n\t//==============================================================================\n\t return {\n\t HDAListItemView : HDAListItemView\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)))\n\n/***/ },\n/* 72 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(70),\n\t __webpack_require__(40),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( DATASET, HISTORY_CONTENT, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\t//==============================================================================\n\tvar _super = DATASET.DatasetAssociation,\n\t hcontentMixin = HISTORY_CONTENT.HistoryContentMixin;\n\t/** @class (HDA) model for a Galaxy dataset contained in and related to a history.\n\t */\n\tvar HistoryDatasetAssociation = _super.extend( BASE_MVC.mixin( hcontentMixin,\n\t/** @lends HistoryDatasetAssociation.prototype */{\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t //logger : console,\n\t\n\t // because all objects have constructors (as this hashmap would even if this next line wasn't present)\n\t // the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model\n\t // - re-apply manually it now\n\t /** call the mixin constructor */\n\t constructor : function( attrs, options ){\n\t hcontentMixin.constructor.call( this, attrs, options );\n\t },\n\t\n\t /** default attributes for a model */\n\t defaults : _.extend( {}, _super.prototype.defaults, hcontentMixin.defaults, {\n\t model_class : 'HistoryDatasetAssociation'\n\t }),\n\t\n\t /** Set up the model, determine if accessible, bind listeners\n\t */\n\t initialize : function( attributes, options ){\n\t _super.prototype.initialize.call( this, attributes, options );\n\t hcontentMixin.initialize.call( this, attributes, options );\n\t },\n\t\n\t // ........................................................................ misc\n\t /** String representation */\n\t toString : function(){\n\t var nameAndId = this.get( 'id' ) || '';\n\t if( this.get( 'name' ) ){\n\t nameAndId = this.get( 'hid' ) + ' :\"' + this.get( 'name' ) + '\",' + nameAndId;\n\t }\n\t return 'HDA(' + nameAndId + ')';\n\t }\n\t}));\n\t\n\t//==============================================================================\n\t return {\n\t HistoryDatasetAssociation : HistoryDatasetAssociation\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)))\n\n/***/ },\n/* 73 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(10),\n\t __webpack_require__(29),\n\t __webpack_require__(68),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( STATES, DC_LI, DC_VIEW, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\t//==============================================================================\n\tvar _super = DC_LI.DCListItemView;\n\t/** @class Read only view for HistoryDatasetCollectionAssociation (a dataset collection inside a history).\n\t */\n\tvar HDCAListItemView = _super.extend(\n\t/** @lends HDCAListItemView.prototype */{\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t //logger : console,\n\t\n\t className : _super.prototype.className + \" history-content\",\n\t\n\t /** event listeners */\n\t _setUpListeners : function(){\n\t _super.prototype._setUpListeners.call( this );\n\t\n\t this.listenTo( this.model, {\n\t 'change:populated change:visible' : function( model, options ){ this.render(); },\n\t });\n\t },\n\t\n\t /** Override to provide the proper collections panels as the foldout */\n\t _getFoldoutPanelClass : function(){\n\t switch( this.model.get( 'collection_type' ) ){\n\t case 'list':\n\t return DC_VIEW.ListCollectionView;\n\t case 'paired':\n\t return DC_VIEW.PairCollectionView;\n\t case 'list:paired':\n\t return DC_VIEW.ListOfPairsCollectionView;\n\t }\n\t throw new TypeError( 'Uknown collection_type: ' + this.model.get( 'collection_type' ) );\n\t },\n\t\n\t /** In this override, add the state as a class for use with state-based CSS */\n\t _swapNewRender : function( $newRender ){\n\t _super.prototype._swapNewRender.call( this, $newRender );\n\t//TODO: model currently has no state\n\t var state = !this.model.get( 'populated' ) ? STATES.RUNNING : STATES.OK;\n\t //if( this.model.has( 'state' ) ){\n\t this.$el.addClass( 'state-' + state );\n\t //}\n\t return this.$el;\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'HDCAListItemView(' + modelString + ')';\n\t }\n\t});\n\t\n\t/** underscore templates */\n\tHDCAListItemView.prototype.templates = (function(){\n\t\n\t var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n\t hidden : BASE_MVC.wrapTemplate([\n\t // add a warning when hidden\n\t '<% if( !collection.visible ){ %>',\n\t '
',\n\t _l( 'This collection has been hidden' ),\n\t '
',\n\t '<% } %>'\n\t ], 'collection' )\n\t });\n\t\n\t// could steal this from hda-base (or use mixed content)\n\t var titleBarTemplate = BASE_MVC.wrapTemplate([\n\t // adding the hid display to the title\n\t '
',\n\t '',\n\t '
',\n\t //TODO: remove whitespace and use margin-right\n\t '<%- collection.hid %> ',\n\t '<%- collection.name %>',\n\t '
',\n\t '',\n\t '
'\n\t ], 'collection' );\n\t\n\t return _.extend( {}, _super.prototype.templates, {\n\t warnings : warnings,\n\t titleBar : titleBarTemplate\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t return {\n\t HDCAListItemView : HDCAListItemView\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)))\n\n/***/ },\n/* 74 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, jQuery, _, $) {\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(41),\n\t __webpack_require__(4),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( HISTORY_CONTENTS, UTILS, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'history';\n\t//==============================================================================\n\t/** @class Model for a Galaxy history resource - both a record of user\n\t * tool use and a collection of the datasets those tools produced.\n\t * @name History\n\t * @augments Backbone.Model\n\t */\n\tvar History = Backbone.Model\n\t .extend( BASE_MVC.LoggableMixin )\n\t .extend( BASE_MVC.mixin( BASE_MVC.SearchableModelMixin, /** @lends History.prototype */{\n\t _logNamespace : logNamespace,\n\t\n\t // values from api (may need more)\n\t defaults : {\n\t model_class : 'History',\n\t id : null,\n\t name : 'Unnamed History',\n\t state : 'new',\n\t\n\t deleted : false\n\t },\n\t\n\t // ........................................................................ urls\n\t urlRoot: Galaxy.root + 'api/histories',\n\t\n\t // ........................................................................ set up/tear down\n\t /** Set up the model\n\t * @param {Object} historyJSON model data for this History\n\t * @param {Object[]} contentsJSON array of model data for this History's contents (hdas or collections)\n\t * @param {Object} options any extra settings including logger\n\t */\n\t initialize : function( historyJSON, contentsJSON, options ){\n\t options = options || {};\n\t this.logger = options.logger || null;\n\t this.log( this + \".initialize:\", historyJSON, contentsJSON, options );\n\t\n\t /** HistoryContents collection of the HDAs contained in this history. */\n\t this.log( 'creating history contents:', contentsJSON );\n\t this.contents = new HISTORY_CONTENTS.HistoryContents( contentsJSON || [], { historyId: this.get( 'id' )});\n\t //// if we've got hdas passed in the constructor, load them\n\t //if( contentsJSON && _.isArray( contentsJSON ) ){\n\t // this.log( 'resetting history contents:', contentsJSON );\n\t // this.contents.reset( contentsJSON );\n\t //}\n\t\n\t this._setUpListeners();\n\t\n\t /** cached timeout id for the dataset updater */\n\t this.updateTimeoutId = null;\n\t // set up update timeout if needed\n\t //this.checkForUpdates();\n\t },\n\t\n\t /** set up any event listeners for this history including those to the contained HDAs\n\t * events: error:contents if an error occurred with the contents collection\n\t */\n\t _setUpListeners : function(){\n\t this.on( 'error', function( model, xhr, options, msg, details ){\n\t this.errorHandler( model, xhr, options, msg, details );\n\t });\n\t\n\t // hda collection listening\n\t if( this.contents ){\n\t this.listenTo( this.contents, 'error', function(){\n\t this.trigger.apply( this, [ 'error:contents' ].concat( jQuery.makeArray( arguments ) ) );\n\t });\n\t }\n\t // if the model's id changes ('current' or null -> an actual id), update the contents history_id\n\t this.on( 'change:id', function( model, newId ){\n\t if( this.contents ){\n\t this.contents.historyId = newId;\n\t }\n\t });\n\t },\n\t\n\t //TODO: see base-mvc\n\t //onFree : function(){\n\t // if( this.contents ){\n\t // this.contents.free();\n\t // }\n\t //},\n\t\n\t /** event listener for errors. Generally errors are handled outside this model */\n\t errorHandler : function( model, xhr, options, msg, details ){\n\t // clear update timeout on model err\n\t this.clearUpdateTimeout();\n\t },\n\t\n\t /** convert size in bytes to a more human readable version */\n\t nice_size : function(){\n\t return UTILS.bytesToString( this.get( 'size' ), true, 2 );\n\t },\n\t\n\t /** override to add nice_size */\n\t toJSON : function(){\n\t return _.extend( Backbone.Model.prototype.toJSON.call( this ), {\n\t nice_size : this.nice_size()\n\t });\n\t },\n\t\n\t /** override to allow getting nice_size */\n\t get : function( key ){\n\t if( key === 'nice_size' ){\n\t return this.nice_size();\n\t }\n\t return Backbone.Model.prototype.get.apply( this, arguments );\n\t },\n\t\n\t // ........................................................................ common queries\n\t /** T/F is this history owned by the current user (Galaxy.user)\n\t * Note: that this will return false for an anon user even if the history is theirs.\n\t */\n\t ownedByCurrUser : function(){\n\t // no currUser\n\t if( !Galaxy || !Galaxy.user ){\n\t return false;\n\t }\n\t // user is anon or history isn't owned\n\t if( Galaxy.user.isAnonymous() || Galaxy.user.id !== this.get( 'user_id' ) ){\n\t return false;\n\t }\n\t return true;\n\t },\n\t\n\t /** */\n\t contentsCount : function(){\n\t return _.reduce( _.values( this.get( 'state_details' ) ), function( memo, num ){ return memo + num; }, 0 );\n\t },\n\t\n\t /** Return the number of running jobs assoc with this history (note: unknown === 0) */\n\t numOfUnfinishedJobs : function(){\n\t var unfinishedJobIds = this.get( 'non_ready_jobs' );\n\t return unfinishedJobIds? unfinishedJobIds.length : 0;\n\t },\n\t\n\t /** Return the number of running hda/hdcas in this history (note: unknown === 0) */\n\t numOfUnfinishedShownContents : function(){\n\t var contents = this.contents.running().visibleAndUndeleted();\n\t return contents? contents.length : 0;\n\t },\n\t\n\t // ........................................................................ search\n\t /** What model fields to search with */\n\t searchAttributes : [\n\t 'name', 'annotation', 'tags'\n\t ],\n\t\n\t /** Adding title and singular tag */\n\t searchAliases : {\n\t title : 'name',\n\t tag : 'tags'\n\t },\n\t\n\t // ........................................................................ updates\n\t _getSizeAndRunning : function(){\n\t return this.fetch({ data : $.param({ keys : 'size,non_ready_jobs' }) });\n\t },\n\t\n\t /** */\n\t refresh : function( options ){\n\t options = options || {};\n\t var self = this;\n\t\n\t var lastUpdateTime = self.lastUpdateTime;\n\t // note: if there was no previous update time, all summary contents will be fetched\n\t return self.contents.fetchUpdated( lastUpdateTime )\n\t .done( function( response, status, xhr ){\n\t var serverResponseDatetime;\n\t try {\n\t serverResponseDatetime = new Date( xhr.getResponseHeader( 'Date' ) );\n\t } catch( err ){}\n\t self.lastUpdateTime = serverResponseDatetime || new Date();\n\t self.checkForUpdates( options );\n\t });\n\t },\n\t\n\t /** */\n\t checkForUpdates : function( options ){\n\t options = options || {};\n\t var delay = History.UPDATE_DELAY;\n\t var self = this;\n\t\n\t function _delayThenUpdate(){\n\t // prevent buildup of updater timeouts by clearing previous if any, then set new and cache id\n\t self.clearUpdateTimeout();\n\t self.updateTimeoutId = setTimeout( function(){\n\t self.refresh( options );\n\t }, delay );\n\t }\n\t\n\t // if there are still datasets in the non-ready state, recurse into this function with the new time\n\t if( this.numOfUnfinishedShownContents() > 0 ){\n\t _delayThenUpdate();\n\t\n\t } else {\n\t // no datasets are running, but currently runnning jobs may still produce new datasets\n\t // see if the history has any running jobs and continue to update if so\n\t // (also update the size for the user in either case)\n\t self._getSizeAndRunning()\n\t .done( function( historyData ){\n\t if( self.numOfUnfinishedJobs() > 0 ){\n\t _delayThenUpdate();\n\t\n\t } else {\n\t // otherwise, let listeners know that all updates have stopped\n\t self.trigger( 'ready' );\n\t }\n\t });\n\t }\n\t },\n\t\n\t /** clear the timeout and the cached timeout id */\n\t clearUpdateTimeout : function(){\n\t if( this.updateTimeoutId ){\n\t clearTimeout( this.updateTimeoutId );\n\t this.updateTimeoutId = null;\n\t }\n\t },\n\t\n\t // ........................................................................ ajax\n\t /** save this history, _Mark_ing it as deleted (just a flag) */\n\t _delete : function( options ){\n\t if( this.get( 'deleted' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: true }, options );\n\t },\n\t /** purge this history, _Mark_ing it as purged and removing all dataset data from the server */\n\t purge : function( options ){\n\t if( this.get( 'purged' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: true, purged: true }, options );\n\t },\n\t /** save this history, _Mark_ing it as undeleted */\n\t undelete : function( options ){\n\t if( !this.get( 'deleted' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: false }, options );\n\t },\n\t\n\t /** Make a copy of this history on the server\n\t * @param {Boolean} current if true, set the copy as the new current history (default: true)\n\t * @param {String} name name of new history (default: none - server sets to: Copy of )\n\t * @fires copied passed this history and the response JSON from the copy\n\t * @returns {xhr}\n\t */\n\t copy : function( current, name, allDatasets ){\n\t current = ( current !== undefined )?( current ):( true );\n\t if( !this.id ){\n\t throw new Error( 'You must set the history ID before copying it.' );\n\t }\n\t\n\t var postData = { history_id : this.id };\n\t if( current ){\n\t postData.current = true;\n\t }\n\t if( name ){\n\t postData.name = name;\n\t }\n\t if( !allDatasets ){\n\t postData.all_datasets = false;\n\t }\n\t\n\t var history = this,\n\t copy = jQuery.post( this.urlRoot, postData );\n\t // if current - queue to setAsCurrent before firing 'copied'\n\t if( current ){\n\t return copy.then( function( response ){\n\t var newHistory = new History( response );\n\t return newHistory.setAsCurrent()\n\t .done( function(){\n\t history.trigger( 'copied', history, response );\n\t });\n\t });\n\t }\n\t return copy.done( function( response ){\n\t history.trigger( 'copied', history, response );\n\t });\n\t },\n\t\n\t setAsCurrent : function(){\n\t var history = this,\n\t xhr = jQuery.getJSON( Galaxy.root + 'history/set_as_current?id=' + this.id );\n\t\n\t xhr.done( function(){\n\t history.trigger( 'set-as-current', history );\n\t });\n\t return xhr;\n\t },\n\t\n\t // ........................................................................ misc\n\t toString : function(){\n\t return 'History(' + this.get( 'id' ) + ',' + this.get( 'name' ) + ')';\n\t }\n\t}));\n\t\n\t//------------------------------------------------------------------------------ CLASS VARS\n\t/** When the history has running hdas,\n\t * this is the amount of time between update checks from the server\n\t */\n\tHistory.UPDATE_DELAY = 4000;\n\t\n\t/** Get data for a history then its hdas using a sequential ajax call, return a deferred to receive both */\n\tHistory.getHistoryData = function getHistoryData( historyId, options ){\n\t options = options || {};\n\t var detailIdsFn = options.detailIdsFn || [];\n\t var hdcaDetailIds = options.hdcaDetailIds || [];\n\t //console.debug( 'getHistoryData:', historyId, options );\n\t\n\t var df = jQuery.Deferred(),\n\t historyJSON = null;\n\t\n\t function getHistory( id ){\n\t // get the history data\n\t if( historyId === 'current' ){\n\t return jQuery.getJSON( Galaxy.root + 'history/current_history_json' );\n\t }\n\t return jQuery.ajax( Galaxy.root + 'api/histories/' + historyId );\n\t }\n\t function isEmpty( historyData ){\n\t // get the number of hdas accrd. to the history\n\t return historyData && historyData.empty;\n\t }\n\t function getContents( historyData ){\n\t // get the hda data\n\t // if no hdas accrd. to history: return empty immed.\n\t if( isEmpty( historyData ) ){ return []; }\n\t // if there are hdas accrd. to history: get those as well\n\t if( _.isFunction( detailIdsFn ) ){\n\t detailIdsFn = detailIdsFn( historyData );\n\t }\n\t if( _.isFunction( hdcaDetailIds ) ){\n\t hdcaDetailIds = hdcaDetailIds( historyData );\n\t }\n\t var data = {\n\t v : 'dev'\n\t };\n\t if( detailIdsFn.length ) {\n\t data.details = detailIdsFn.join( ',' );\n\t }\n\t return jQuery.ajax( Galaxy.root + 'api/histories/' + historyData.id + '/contents', { data: data });\n\t }\n\t\n\t // getting these concurrently is 400% slower (sqlite, local, vanilla) - so:\n\t // chain the api calls - getting history first then contents\n\t\n\t var historyFn = options.historyFn || getHistory,\n\t contentsFn = options.contentsFn || getContents;\n\t\n\t // chain ajax calls: get history first, then hdas\n\t var historyXHR = historyFn( historyId );\n\t historyXHR.done( function( json ){\n\t // set outer scope var here for use below\n\t historyJSON = json;\n\t df.notify({ status: 'history data retrieved', historyJSON: historyJSON });\n\t });\n\t historyXHR.fail( function( xhr, status, message ){\n\t // call reject on the outer deferred to allow its fail callback to run\n\t df.reject( xhr, 'loading the history' );\n\t });\n\t\n\t var contentsXHR = historyXHR.then( contentsFn );\n\t contentsXHR.then( function( contentsJSON ){\n\t df.notify({ status: 'contents data retrieved', historyJSON: historyJSON, contentsJSON: contentsJSON });\n\t // we've got both: resolve the outer scope deferred\n\t df.resolve( historyJSON, contentsJSON );\n\t });\n\t contentsXHR.fail( function( xhr, status, message ){\n\t // call reject on the outer deferred to allow its fail callback to run\n\t df.reject( xhr, 'loading the contents', { history: historyJSON } );\n\t });\n\t\n\t return df;\n\t};\n\t\n\t\n\t//==============================================================================\n\tvar ControlledFetchMixin = {\n\t\n\t /** Override to convert certain options keys into API index parameters */\n\t fetch : function( options ){\n\t options = options || {};\n\t options.data = options.data || this._buildFetchData( options );\n\t // use repeated params for arrays, e.g. q=1&qv=1&q=2&qv=2\n\t options.traditional = true;\n\t return Backbone.Collection.prototype.fetch.call( this, options );\n\t },\n\t\n\t /** These attribute keys are valid params to fetch/API-index */\n\t _fetchOptions : [\n\t /** model dependent string to control the order of models returned */\n\t 'order',\n\t /** limit the number of models returned from a fetch */\n\t 'limit',\n\t /** skip this number of models when fetching */\n\t 'offset',\n\t /** what series of attributes to return (model dependent) */\n\t 'view',\n\t /** individual keys to return for the models (see api/histories.index) */\n\t 'keys'\n\t ],\n\t\n\t /** Build the data dictionary to send to fetch's XHR as data */\n\t _buildFetchData : function( options ){\n\t var data = {},\n\t fetchDefaults = this._fetchDefaults();\n\t options = _.defaults( options || {}, fetchDefaults );\n\t data = _.pick( options, this._fetchOptions );\n\t\n\t var filters = _.has( options, 'filters' )? options.filters : ( fetchDefaults.filters || {} );\n\t if( !_.isEmpty( filters ) ){\n\t _.extend( data, this._buildFetchFilters( filters ) );\n\t }\n\t return data;\n\t },\n\t\n\t /** Override to have defaults for fetch options and filters */\n\t _fetchDefaults : function(){\n\t // to be overridden\n\t return {};\n\t },\n\t\n\t /** Convert dictionary filters to qqv style arrays */\n\t _buildFetchFilters : function( filters ){\n\t var filterMap = {\n\t q : [],\n\t qv : []\n\t };\n\t _.each( filters, function( v, k ){\n\t if( v === true ){ v = 'True'; }\n\t if( v === false ){ v = 'False'; }\n\t if( v === null ){ v = 'None'; }\n\t filterMap.q.push( k );\n\t filterMap.qv.push( v );\n\t });\n\t return filterMap;\n\t },\n\t};\n\t\n\t//==============================================================================\n\t/** @class A collection of histories (per user).\n\t * (stub) currently unused.\n\t */\n\tvar HistoryCollection = Backbone.Collection\n\t .extend( BASE_MVC.LoggableMixin )\n\t .extend( ControlledFetchMixin )\n\t .extend(/** @lends HistoryCollection.prototype */{\n\t _logNamespace : logNamespace,\n\t\n\t model : History,\n\t\n\t /** @type {String} the default sortOrders key for sorting */\n\t DEFAULT_ORDER : 'update_time',\n\t\n\t /** @type {Object} map of collection sorting orders generally containing a getter to return the attribute\n\t * sorted by and asc T/F if it is an ascending sort.\n\t */\n\t sortOrders : {\n\t 'update_time' : {\n\t getter : function( h ){ return new Date( h.get( 'update_time' ) ); },\n\t asc : false\n\t },\n\t 'update_time-asc' : {\n\t getter : function( h ){ return new Date( h.get( 'update_time' ) ); },\n\t asc : true\n\t },\n\t 'name' : {\n\t getter : function( h ){ return h.get( 'name' ); },\n\t asc : true\n\t },\n\t 'name-dsc' : {\n\t getter : function( h ){ return h.get( 'name' ); },\n\t asc : false\n\t },\n\t 'size' : {\n\t getter : function( h ){ return h.get( 'size' ); },\n\t asc : false\n\t },\n\t 'size-asc' : {\n\t getter : function( h ){ return h.get( 'size' ); },\n\t asc : true\n\t }\n\t },\n\t\n\t initialize : function( models, options ){\n\t options = options || {};\n\t this.log( 'HistoryCollection.initialize', arguments );\n\t\n\t // instance vars\n\t /** @type {boolean} should deleted histories be included */\n\t this.includeDeleted = options.includeDeleted || false;\n\t // set the sort order\n\t this.setOrder( options.order || this.DEFAULT_ORDER );\n\t /** @type {String} encoded id of the history that's current */\n\t this.currentHistoryId = options.currentHistoryId;\n\t /** @type {boolean} have all histories been fetched and in the collection? */\n\t this.allFetched = options.allFetched || false;\n\t\n\t // this.on( 'all', function(){\n\t // console.info( 'event:', arguments );\n\t // });\n\t this.setUpListeners();\n\t },\n\t\n\t urlRoot : Galaxy.root + 'api/histories',\n\t url : function(){ return this.urlRoot; },\n\t\n\t /** returns map of default filters and settings for fetching from the API */\n\t _fetchDefaults : function(){\n\t // to be overridden\n\t var defaults = {\n\t order : this.order,\n\t view : 'detailed'\n\t };\n\t if( !this.includeDeleted ){\n\t defaults.filters = {\n\t deleted : false,\n\t purged : false,\n\t };\n\t } else {\n\t defaults.filters = {\n\t // TODO: for bypassing defaults on current API\n\t deleted : null,\n\t };\n\t }\n\t return defaults;\n\t },\n\t\n\t /** set up reflexive event handlers */\n\t setUpListeners : function setUpListeners(){\n\t this.on({\n\t // when a history is deleted, remove it from the collection (if optionally set to do so)\n\t 'change:deleted' : function( history ){\n\t // TODO: this becomes complicated when more filters are used\n\t this.debug( 'change:deleted', this.includeDeleted, history.get( 'deleted' ) );\n\t if( !this.includeDeleted && history.get( 'deleted' ) ){\n\t this.remove( history );\n\t }\n\t },\n\t // listen for a history copy, setting it to current\n\t 'copied' : function( original, newData ){\n\t this.setCurrent( new History( newData, [] ) );\n\t },\n\t // when a history is made current, track the id in the collection\n\t 'set-as-current' : function( history ){\n\t var oldCurrentId = this.currentHistoryId;\n\t this.trigger( 'no-longer-current', oldCurrentId );\n\t this.currentHistoryId = history.id;\n\t }\n\t });\n\t },\n\t\n\t /** override to allow passing options.order and setting the sort order to one of sortOrders */\n\t sort : function( options ){\n\t options = options || {};\n\t this.setOrder( options.order );\n\t return Backbone.Collection.prototype.sort.call( this, options );\n\t },\n\t\n\t /** build the comparator used to sort this collection using the sortOrder map and the given order key\n\t * @event 'changed-order' passed the new order and the collection\n\t */\n\t setOrder : function( order ){\n\t var collection = this,\n\t sortOrder = this.sortOrders[ order ];\n\t if( _.isUndefined( sortOrder ) ){ return; }\n\t\n\t collection.order = order;\n\t collection.comparator = function comparator( a, b ){\n\t var currentHistoryId = collection.currentHistoryId;\n\t // current always first\n\t if( a.id === currentHistoryId ){ return -1; }\n\t if( b.id === currentHistoryId ){ return 1; }\n\t // then compare by an attribute\n\t a = sortOrder.getter( a );\n\t b = sortOrder.getter( b );\n\t return sortOrder.asc?\n\t ( ( a === b )?( 0 ):( a > b ? 1 : -1 ) ):\n\t ( ( a === b )?( 0 ):( a > b ? -1 : 1 ) );\n\t };\n\t collection.trigger( 'changed-order', collection.order, collection );\n\t return collection;\n\t },\n\t\n\t /** override to provide order and offsets based on instance vars, set limit if passed,\n\t * and set allFetched/fire 'all-fetched' when xhr returns\n\t */\n\t fetch : function( options ){\n\t options = options || {};\n\t if( this.allFetched ){ return jQuery.when({}); }\n\t var collection = this,\n\t fetchOptions = _.defaults( options, {\n\t remove : false,\n\t offset : collection.length >= 1? ( collection.length - 1 ) : 0,\n\t order : collection.order\n\t }),\n\t limit = options.limit;\n\t if( !_.isUndefined( limit ) ){\n\t fetchOptions.limit = limit;\n\t }\n\t\n\t return ControlledFetchMixin.fetch.call( this, fetchOptions )\n\t .done( function _postFetchMore( fetchData ){\n\t var numFetched = _.isArray( fetchData )? fetchData.length : 0;\n\t // anything less than a full page means we got all there is to get\n\t if( !limit || numFetched < limit ){\n\t collection.allFetched = true;\n\t collection.trigger( 'all-fetched', collection );\n\t }\n\t }\n\t );\n\t },\n\t\n\t /** create a new history and by default set it to be the current history */\n\t create : function create( data, hdas, historyOptions, xhrOptions ){\n\t //TODO: .create is actually a collection function that's overridden here\n\t var collection = this,\n\t xhr = jQuery.getJSON( Galaxy.root + 'history/create_new_current' );\n\t return xhr.done( function( newData ){\n\t collection.setCurrent( new History( newData, [], historyOptions || {} ) );\n\t });\n\t },\n\t\n\t /** set the current history to the given history, placing it first in the collection.\n\t * Pass standard bbone options for use in unshift.\n\t * @triggers new-current passed history and this collection\n\t */\n\t setCurrent : function( history, options ){\n\t options = options || {};\n\t // new histories go in the front\n\t this.unshift( history, options );\n\t this.currentHistoryId = history.get( 'id' );\n\t if( !options.silent ){\n\t this.trigger( 'new-current', history, this );\n\t }\n\t return this;\n\t },\n\t\n\t /** override to reset allFetched flag to false */\n\t reset : function( models, options ){\n\t this.allFetched = false;\n\t return Backbone.Collection.prototype.reset.call( this, models, options );\n\t },\n\t\n\t toString: function toString(){\n\t return 'HistoryCollection(' + this.length + ')';\n\t }\n\t});\n\t\n\t//==============================================================================\n\treturn {\n\t History : History,\n\t HistoryCollection : HistoryCollection\n\t};}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 75 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, $) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(42),\n\t __webpack_require__(118),\n\t __webpack_require__(6),\n\t __webpack_require__(5),\n\t __webpack_require__(82)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( LIST_ITEM, LoadingIndicator, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'list';\n\t/* ============================================================================\n\tTODO:\n\t\n\t============================================================================ */\n\t/** @class View for a list/collection of models and the sub-views of those models.\n\t * Sub-views must (at least have the interface if not) inherit from ListItemView.\n\t * (For a list panel that also includes some 'container' model (History->HistoryContents)\n\t * use ModelWithListPanel)\n\t *\n\t * Allows for:\n\t * searching collection/sub-views\n\t * selecting/multi-selecting sub-views\n\t *\n\t * Currently used:\n\t * for dataset/dataset-choice\n\t * as superclass of ModelListPanel\n\t */\n\tvar ListPanel = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend(/** @lends ListPanel.prototype */{\n\t _logNamespace : logNamespace,\n\t\n\t /** class to use for constructing the sub-views */\n\t viewClass : LIST_ITEM.ListItemView,\n\t /** class to used for constructing collection of sub-view models */\n\t collectionClass : Backbone.Collection,\n\t\n\t tagName : 'div',\n\t className : 'list-panel',\n\t\n\t /** (in ms) that jquery effects will use */\n\t fxSpeed : 'fast',\n\t\n\t /** string to display when the collection has no contents */\n\t emptyMsg : _l( 'This list is empty' ),\n\t /** displayed when no items match the search terms */\n\t noneFoundMsg : _l( 'No matching items found' ),\n\t /** string used for search placeholder */\n\t searchPlaceholder : _l( 'search' ),\n\t\n\t // ......................................................................... SET UP\n\t /** Set up the view, set up storage, bind listeners to HistoryContents events\n\t * @param {Object} attributes optional settings for the list\n\t */\n\t initialize : function( attributes, options ){\n\t attributes = attributes || {};\n\t // set the logger if requested\n\t if( attributes.logger ){\n\t this.logger = attributes.logger;\n\t }\n\t this.log( this + '.initialize:', attributes );\n\t\n\t // ---- instance vars\n\t /** how quickly should jquery fx run? */\n\t this.fxSpeed = _.has( attributes, 'fxSpeed' )?( attributes.fxSpeed ):( this.fxSpeed );\n\t\n\t /** filters for displaying subviews */\n\t this.filters = [];\n\t /** current search terms */\n\t this.searchFor = attributes.searchFor || '';\n\t\n\t /** loading indicator */\n\t this.indicator = new LoadingIndicator( this.$el );\n\t\n\t /** currently showing selectors on items? */\n\t this.selecting = ( attributes.selecting !== undefined )? attributes.selecting : true;\n\t //this.selecting = false;\n\t\n\t /** cached selected item.model.ids to persist btwn renders */\n\t this.selected = attributes.selected || [];\n\t /** the last selected item.model.id */\n\t this.lastSelected = null;\n\t\n\t /** are sub-views draggable */\n\t this.dragItems = attributes.dragItems || false;\n\t\n\t /** list item view class (when passed models) */\n\t this.viewClass = attributes.viewClass || this.viewClass;\n\t\n\t /** list item views */\n\t this.views = [];\n\t /** list item models */\n\t this.collection = attributes.collection || ( new this.collectionClass([]) );\n\t\n\t /** filter fns run over collection items to see if they should show in the list */\n\t this.filters = attributes.filters || [];\n\t\n\t /** override $scrollContainer fn via attributes - fn should return jq for elem to call scrollTo on */\n\t this.$scrollContainer = attributes.$scrollContainer || this.$scrollContainer;\n\t\n\t//TODO: remove\n\t this.title = attributes.title || '';\n\t this.subtitle = attributes.subtitle || '';\n\t\n\t this._setUpListeners();\n\t },\n\t\n\t /** free any sub-views the list has */\n\t freeViews : function(){\n\t//TODO: stopListening? remove?\n\t _.each( this.views, function( view ){\n\t view.off();\n\t });\n\t this.views = [];\n\t return this;\n\t },\n\t\n\t // ------------------------------------------------------------------------ listeners\n\t /** create any event listeners for the list\n\t */\n\t _setUpListeners : function(){\n\t this.off();\n\t\n\t //TODO: move errorHandler down into list-view from history-view or\n\t // pass to global error handler (Galaxy)\n\t this.on({\n\t error: function( model, xhr, options, msg, details ){\n\t //this.errorHandler( model, xhr, options, msg, details );\n\t console.error( model, xhr, options, msg, details );\n\t },\n\t // show hide the loading indicator\n\t loading: function(){\n\t this._showLoadingIndicator( 'loading...', 40 );\n\t },\n\t 'loading-done': function(){\n\t this._hideLoadingIndicator( 40 );\n\t },\n\t });\n\t\n\t // throw the first render up as a diff namespace using once (for outside consumption)\n\t this.once( 'rendered', function(){\n\t this.trigger( 'rendered:initial', this );\n\t });\n\t\n\t // debugging\n\t if( this.logger ){\n\t this.on( 'all', function( event ){\n\t this.log( this + '', arguments );\n\t });\n\t }\n\t\n\t this._setUpCollectionListeners();\n\t this._setUpViewListeners();\n\t return this;\n\t },\n\t\n\t /** listening for collection events */\n\t _setUpCollectionListeners : function(){\n\t this.log( this + '._setUpCollectionListeners', this.collection );\n\t this.collection.off();\n\t\n\t // bubble up error events\n\t this.listenTo( this.collection, {\n\t error : function( model, xhr, options, msg, details ){\n\t this.trigger( 'error', model, xhr, options, msg, details );\n\t },\n\t reset : function(){\n\t this.renderItems();\n\t },\n\t add : this.addItemView,\n\t remove : this.removeItemView\n\t });\n\t\n\t // debugging\n\t if( this.logger ){\n\t this.listenTo( this.collection, 'all', function( event ){\n\t this.info( this + '(collection)', arguments );\n\t });\n\t }\n\t return this;\n\t },\n\t\n\t /** listening for sub-view events that bubble up with the 'view:' prefix */\n\t _setUpViewListeners : function(){\n\t this.log( this + '._setUpViewListeners' );\n\t\n\t // shift to select a range\n\t this.on({\n\t 'view:selected': function( view, ev ){\n\t if( ev && ev.shiftKey && this.lastSelected ){\n\t var lastSelectedView = this.viewFromModelId( this.lastSelected );\n\t if( lastSelectedView ){\n\t this.selectRange( view, lastSelectedView );\n\t }\n\t } else if( ev && ev.altKey && !this.selecting ){\n\t this.showSelectors();\n\t }\n\t this.selected.push( view.model.id );\n\t this.lastSelected = view.model.id;\n\t },\n\t\n\t 'view:de-selected': function( view, ev ){\n\t this.selected = _.without( this.selected, view.model.id );\n\t //this.lastSelected = view.model.id;\n\t }\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ rendering\n\t /** Render this content, set up ui.\n\t * @param {Number or String} speed the speed of the render\n\t */\n\t render : function( speed ){\n\t this.log( this + '.render', speed );\n\t var $newRender = this._buildNewRender();\n\t this._setUpBehaviors( $newRender );\n\t this._queueNewRender( $newRender, speed );\n\t return this;\n\t },\n\t\n\t /** Build a temp div containing the new children for the view's $el.\n\t */\n\t _buildNewRender : function(){\n\t this.debug( this + '(ListPanel)._buildNewRender' );\n\t var $newRender = $( this.templates.el( {}, this ) );\n\t this._renderControls( $newRender );\n\t this._renderTitle( $newRender );\n\t this._renderSubtitle( $newRender );\n\t this._renderSearch( $newRender );\n\t this.renderItems( $newRender );\n\t return $newRender;\n\t },\n\t\n\t /** Build a temp div containing the new children for the view's $el.\n\t */\n\t _renderControls : function( $newRender ){\n\t this.debug( this + '(ListPanel)._renderControls' );\n\t var $controls = $( this.templates.controls( {}, this ) );\n\t $newRender.find( '.controls' ).replaceWith( $controls );\n\t return $controls;\n\t },\n\t\n\t /**\n\t */\n\t _renderTitle : function( $where ){\n\t //$where = $where || this.$el;\n\t //$where.find( '.title' ).replaceWith( ... )\n\t },\n\t\n\t /**\n\t */\n\t _renderSubtitle : function( $where ){\n\t //$where = $where || this.$el;\n\t //$where.find( '.title' ).replaceWith( ... )\n\t },\n\t\n\t /** Fade out the old el, swap in the new contents, then fade in.\n\t * @param {Number or String} speed jq speed to use for rendering effects\n\t * @fires rendered when rendered\n\t */\n\t _queueNewRender : function( $newRender, speed ) {\n\t speed = ( speed === undefined )?( this.fxSpeed ):( speed );\n\t var panel = this;\n\t panel.log( '_queueNewRender:', $newRender, speed );\n\t\n\t $( panel ).queue( 'fx', [\n\t function( next ){ this.$el.fadeOut( speed, next ); },\n\t function( next ){\n\t panel._swapNewRender( $newRender );\n\t next();\n\t },\n\t function( next ){ this.$el.fadeIn( speed, next ); },\n\t function( next ){\n\t panel.trigger( 'rendered', panel );\n\t next();\n\t }\n\t ]);\n\t },\n\t\n\t /** empty out the current el, move the $newRender's children in */\n\t _swapNewRender : function( $newRender ){\n\t this.$el.empty().attr( 'class', this.className ).append( $newRender.children() );\n\t if( this.selecting ){ this.showSelectors( 0 ); }\n\t return this;\n\t },\n\t\n\t /** */\n\t _setUpBehaviors : function( $where ){\n\t $where = $where || this.$el;\n\t $where.find( '.controls [title]' ).tooltip({ placement: 'bottom' });\n\t // set up the pupup for actions available when multi selecting\n\t this._renderMultiselectActionMenu( $where );\n\t return this;\n\t },\n\t\n\t /** render a menu containing the actions available to sets of selected items */\n\t _renderMultiselectActionMenu : function( $where ){\n\t $where = $where || this.$el;\n\t var $menu = $where.find( '.list-action-menu' ),\n\t actions = this.multiselectActions();\n\t if( !actions.length ){\n\t return $menu.empty();\n\t }\n\t\n\t var $newMenu = $([\n\t '
',\n\t '',\n\t '
', '
',\n\t '
'\n\t ].join(''));\n\t var $actions = actions.map( function( action ){\n\t var html = [ '
'\n\t ]);\n\t\n\t return {\n\t el : elTemplate,\n\t controls : controlsTemplate\n\t };\n\t}());\n\t\n\t\n\t//=============================================================================\n\t/** View for a model that has a sub-collection (e.g. History, DatasetCollection)\n\t * Allows:\n\t * the model to be reset\n\t * auto assign panel.collection to panel.model[ panel.modelCollectionKey ]\n\t *\n\t */\n\tvar ModelListPanel = ListPanel.extend({\n\t\n\t /** key of attribute in model to assign to this.collection */\n\t modelCollectionKey : 'contents',\n\t\n\t initialize : function( attributes ){\n\t ListPanel.prototype.initialize.call( this, attributes );\n\t this.selecting = ( attributes.selecting !== undefined )? attributes.selecting : false;\n\t\n\t this.setModel( this.model, attributes );\n\t },\n\t\n\t /** release/free/shutdown old models and set up panel for new models\n\t * @fires new-model with the panel as parameter\n\t */\n\t setModel : function( model, attributes ){\n\t attributes = attributes || {};\n\t this.debug( this + '.setModel:', model, attributes );\n\t\n\t this.freeModel();\n\t this.freeViews();\n\t\n\t if( model ){\n\t var oldModelId = this.model? this.model.get( 'id' ): null;\n\t\n\t // set up the new model with user, logger, storage, events\n\t this.model = model;\n\t if( this.logger ){\n\t this.model.logger = this.logger;\n\t }\n\t this._setUpModelListeners();\n\t\n\t//TODO: relation btwn model, collection becoming tangled here\n\t // free the collection, and assign the new collection to either\n\t // the model[ modelCollectionKey ], attributes.collection, or an empty vanilla collection\n\t this.collection.off();\n\t this.collection = ( this.model[ this.modelCollectionKey ] )?\n\t this.model[ this.modelCollectionKey ]:\n\t ( attributes.collection || ( new this.collectionClass([]) ) );\n\t this._setUpCollectionListeners();\n\t\n\t if( oldModelId && model.get( 'id' ) !== oldModelId ){\n\t this.trigger( 'new-model', this );\n\t }\n\t }\n\t return this;\n\t },\n\t\n\t /** free the current model and all listeners for it, free any views for the model */\n\t freeModel : function(){\n\t // stop/release the previous model, and clear cache to sub-views\n\t if( this.model ){\n\t this.stopListening( this.model );\n\t //TODO: see base-mvc\n\t //this.model.free();\n\t //this.model = null;\n\t }\n\t return this;\n\t },\n\t\n\t // ------------------------------------------------------------------------ listening\n\t /** listening for model events */\n\t _setUpModelListeners : function(){\n\t // override\n\t this.log( this + '._setUpModelListeners', this.model );\n\t // bounce model errors up to the panel\n\t this.listenTo( this.model, 'error', function(){\n\t var args = Array.prototype.slice.call( arguments, 0 );\n\t //args.unshift( 'model:error' );\n\t args.unshift( 'error' );\n\t this.trigger.apply( this, args );\n\t }, this );\n\t return this;\n\t },\n\t\n\t /** Build a temp div containing the new children for the view's $el.\n\t */\n\t _renderControls : function( $newRender ){\n\t this.debug( this + '(ListPanel)._renderControls' );\n\t var json = this.model? this.model.toJSON() : {},\n\t $controls = $( this.templates.controls( json, this ) );\n\t $newRender.find( '.controls' ).replaceWith( $controls );\n\t return $controls;\n\t },\n\t\n\t // ------------------------------------------------------------------------ misc\n\t /** Return a string rep of the panel */\n\t toString : function(){\n\t return 'ModelListPanel(' + this.model + ')';\n\t }\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tModelListPanel.prototype.templates = (function(){\n\t//TODO: move to require text! plugin\n\t\n\t var controlsTemplate = BASE_MVC.wrapTemplate([\n\t '
',\n\t '
',\n\t//TODO: this is really the only difference - consider factoring titlebar out\n\t '
<%- model.name %>
',\n\t '
',\n\t '
<%- view.subtitle %>
',\n\t '',\n\t '',\n\t\n\t '
',\n\t '',\n\t '
',\n\t\n\t '
',\n\t '
',\n\t '',\n\t '',\n\t '
',\n\t '
',\n\t '
',\n\t '
',\n\t '
'\n\t ]);\n\t\n\t return _.extend( _.clone( ListPanel.prototype.templates ), {\n\t controls : controlsTemplate\n\t });\n\t}());\n\t\n\t\n\t//=============================================================================\n\t return {\n\t ListPanel : ListPanel,\n\t ModelListPanel : ModelListPanel\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 76 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( baseMVC, _l ){\n\t// =============================================================================\n\t/** A view on any model that has a 'tags' attribute (a list of tag strings)\n\t * Incorporates the select2 jQuery plugin for tags display/editing:\n\t * http://ivaynberg.github.io/select2/\n\t */\n\tvar TagsEditor = Backbone.View\n\t .extend( baseMVC.LoggableMixin )\n\t .extend( baseMVC.HiddenUntilActivatedViewMixin ).extend({\n\t\n\t tagName : 'div',\n\t className : 'tags-display',\n\t\n\t /** Set up listeners, parse options */\n\t initialize : function( options ){\n\t //console.debug( this, options );\n\t // only listen to the model only for changes to tags - re-render\n\t this.listenTo( this.model, 'change:tags', function(){\n\t this.render();\n\t });\n\t this.hiddenUntilActivated( options.$activator, options );\n\t },\n\t\n\t /** Build the DOM elements, call select to on the created input, and set up behaviors */\n\t render : function(){\n\t var view = this;\n\t this.$el.html( this._template() );\n\t\n\t this.$input().select2({\n\t placeholder : 'Add tags',\n\t width : '100%',\n\t tags : function(){\n\t // initialize possible tags in the dropdown based on all the tags the user has used so far\n\t return view._getTagsUsed();\n\t }\n\t });\n\t\n\t this._setUpBehaviors();\n\t return this;\n\t },\n\t\n\t /** @returns {String} the html text used to build the view's DOM */\n\t _template : function(){\n\t return [\n\t //TODO: make prompt optional\n\t '',\n\t // set up initial tags by adding as CSV to input vals (necc. to init select2)\n\t ''\n\t ].join( '' );\n\t },\n\t\n\t /** @returns {String} the sorted, comma-separated tags from the model */\n\t tagsToCSV : function(){\n\t var tagsArray = this.model.get( 'tags' );\n\t if( !_.isArray( tagsArray ) || _.isEmpty( tagsArray ) ){\n\t return '';\n\t }\n\t return tagsArray.map( function( tag ){\n\t return _.escape( tag );\n\t }).sort().join( ',' );\n\t },\n\t\n\t /** @returns {jQuery} the input for this view */\n\t $input : function(){\n\t return this.$el.find( 'input.tags-input' );\n\t },\n\t\n\t /** @returns {String[]} all tags used by the current user */\n\t _getTagsUsed : function(){\n\t//TODO: global\n\t return Galaxy.user.get( 'tags_used' );\n\t },\n\t\n\t /** set up any event listeners on the view's DOM (mostly handled by select2) */\n\t _setUpBehaviors : function(){\n\t var view = this;\n\t this.$input().on( 'change', function( event ){\n\t // save the model's tags in either remove or added event\n\t view.model.save({ tags: event.val }, { silent: true });\n\t // if it's new, add the tag to the users tags\n\t if( event.added ){\n\t //??: solve weird behavior in FF on test.galaxyproject.org where\n\t // event.added.text is string object: 'String{ 0=\"o\", 1=\"n\", 2=\"e\" }'\n\t view._addNewTagToTagsUsed( event.added.text + '' );\n\t }\n\t });\n\t },\n\t\n\t /** add a new tag (if not already there) to the list of all tags used by the user\n\t * @param {String} newTag the tag to add to the list of used\n\t */\n\t _addNewTagToTagsUsed : function( newTag ){\n\t//TODO: global\n\t var tagsUsed = Galaxy.user.get( 'tags_used' );\n\t if( !_.contains( tagsUsed, newTag ) ){\n\t tagsUsed.push( newTag );\n\t tagsUsed.sort();\n\t Galaxy.user.set( 'tags_used', tagsUsed );\n\t }\n\t },\n\t\n\t /** shut down event listeners and remove this view's DOM */\n\t remove : function(){\n\t this.$input.off();\n\t this.stopListening( this.model );\n\t Backbone.View.prototype.remove.call( this );\n\t },\n\t\n\t /** string rep */\n\t toString : function(){ return [ 'TagsEditor(', this.model + '', ')' ].join(''); }\n\t});\n\t\n\t// =============================================================================\n\treturn {\n\t TagsEditor : TagsEditor\n\t};\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2)))\n\n/***/ },\n/* 77 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $) {/** This renders the content of the ftp popup **/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4)], __WEBPACK_AMD_DEFINE_RESULT__ = function(Utils) {\n\treturn Backbone.View.extend({\n\t // render\n\t initialize: function(options) {\n\t // link options\n\t this.options = Utils.merge(options, {\n\t class_add : 'upload-icon-button fa fa-square-o',\n\t class_remove : 'upload-icon-button fa fa-check-square-o',\n\t class_partial : 'upload-icon-button fa fa-minus-square-o',\n\t collection : null,\n\t onchange : function() {},\n\t onadd : function() {},\n\t onremove : function() {}\n\t });\n\t\n\t // link this\n\t var self = this;\n\t\n\t // link app\n\t this.collection = this.options.collection;\n\t\n\t // set template\n\t this.setElement(this._template());\n\t\n\t // list of rows\n\t this.rows = [];\n\t\n\t // load extension\n\t Utils.get({\n\t url : Galaxy.root + 'api/remote_files',\n\t success : function(ftp_files) { self._fill(ftp_files); },\n\t error : function() { self._fill(); }\n\t });\n\t },\n\t\n\t // fill table\n\t _fill: function(ftp_files) {\n\t if (ftp_files && ftp_files.length > 0) {\n\t // add table\n\t this.$el.find('#upload-ftp-content').html($(this._templateTable()));\n\t\n\t // add files to table\n\t var size = 0;\n\t for (index in ftp_files) {\n\t this.rows.push(this._add(ftp_files[index]));\n\t size += ftp_files[index].size;\n\t }\n\t\n\t // update stats\n\t this.$el.find('#upload-ftp-number').html(ftp_files.length + ' files');\n\t this.$el.find('#upload-ftp-disk').html(Utils.bytesToString (size, true));\n\t\n\t // add event handler to select/unselect all\n\t if (this.collection) {\n\t var self = this;\n\t this.$('._has_collection').show();\n\t this.$select_all = $('#upload-selectall');\n\t this.$select_all.addClass(this.options.class_add);\n\t this.$select_all.on('click', function() {\n\t var add = self.$select_all.hasClass(self.options.class_add);\n\t for (index in ftp_files) {\n\t var ftp_file = ftp_files[index];\n\t var model_index = self._find(ftp_file);\n\t if(!model_index && add || model_index && !add) {\n\t self.rows[index].trigger('click');\n\t }\n\t }\n\t });\n\t this._refresh();\n\t }\n\t } else {\n\t this.$el.find('#upload-ftp-content').html($(this._templateInfo()));\n\t }\n\t this.$el.find('#upload-ftp-wait').hide();\n\t },\n\t\n\t // add\n\t _add: function(ftp_file) {\n\t // link this\n\t var self = this;\n\t\n\t // create new item\n\t var $it = $(this._templateRow(ftp_file));\n\t\n\t // identify icon\n\t var $icon = $it.find('.icon');\n\t\n\t // append to table\n\t $(this.el).find('tbody').append($it);\n\t\n\t // collection mode with add/remove triggers\n\t if (this.collection) {\n\t // find model and set initial 'add' icon class\n\t var icon_class = '';\n\t if (this._find(ftp_file)) {\n\t icon_class = this.options.class_remove;\n\t } else {\n\t icon_class = this.options.class_add;\n\t }\n\t $icon.addClass(icon_class);\n\t\n\t // click triggers add/remove events\n\t $it.on('click', function() {\n\t var model_index = self._find(ftp_file);\n\t $icon.removeClass();\n\t if (!model_index) {\n\t self.options.onadd(ftp_file);\n\t $icon.addClass(self.options.class_remove);\n\t } else {\n\t self.options.onremove(model_index);\n\t $icon.addClass(self.options.class_add);\n\t }\n\t self._refresh();\n\t });\n\t } else {\n\t // click triggers only change\n\t $it.on('click', function() {\n\t self.options.onchange(ftp_file);\n\t });\n\t }\n\t\n\t // return dom handler\n\t return $it;\n\t },\n\t\n\t // refresh\n\t _refresh: function() {\n\t var filtered = this.collection.where({file_mode: 'ftp', enabled: true});\n\t this.$select_all.removeClass();\n\t if (filtered.length == 0) {\n\t this.$select_all.addClass(this.options.class_add);\n\t } else {\n\t if (filtered.length == this.rows.length) {\n\t this.$select_all.addClass(this.options.class_remove);\n\t } else {\n\t this.$select_all.addClass(this.options.class_partial);\n\t }\n\t }\n\t },\n\t\n\t // get model index\n\t _find: function(ftp_file) {\n\t var item = this.collection.findWhere({\n\t file_path : ftp_file.path,\n\t file_mode : 'ftp',\n\t enabled : true\n\t });\n\t return item && item.get('id');\n\t },\n\t\n\t // template row\n\t _templateRow: function(options) {\n\t return '
This Galaxy server allows you to upload files via FTP. To upload some files, log in to the FTP server at ' + this.options.ftp_upload_site + ' using your Galaxy credentials (email address and password).
' +\n\t '
' +\n\t '
';\n\t }\n\t});\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1)))\n\n/***/ },\n/* 78 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $, _) {/** This renders the content of the settings popup, allowing users to specify flags i.e. for space-to-tab conversion **/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils ) {\n\treturn Backbone.View.extend({\n\t options: {\n\t class_check : 'fa-check-square-o',\n\t class_uncheck : 'fa-square-o',\n\t parameters : [{\n\t id : 'space_to_tab',\n\t title : 'Convert spaces to tabs',\n\t },{\n\t id : 'to_posix_lines',\n\t title : 'Use POSIX standard'\n\t }]\n\t },\n\t\n\t initialize: function( options ) {\n\t var self = this;\n\t this.model = options.model;\n\t this.setElement( $( '' ).addClass( 'upload-settings' ) );\n\t this.$el.append( $( '' ).addClass( 'upload-settings-cover' ) );\n\t this.$el.append( $( '