ui: adding PopupMenu view as alternate to make_popup_menu and allow changing option text, adding/removing options, and adding checkmarks for togglable options; index.mako: use PopupMenu for history-options-button; history panel: use alternate_history.mako to add 'collapse all', 'Include hidden datasets'(toggle), 'Include deleted datasets'(toggle) to history-options-button; alternate_history.mako: include better system for debugging js when changing source directly is problematic (main/test); pack_scripts;

This commit is contained in:
Carl Eberhard
2012-12-07 13:56:53 -05:00
parent 9f657f2f1f
commit b52e6d4113
19 changed files with 601 additions and 58 deletions
@@ -98,7 +98,6 @@ class RootController( BaseUIController, UsesHistoryMixin, UsesAnnotations ):
return trans.fill_template_mako( "/my_data.mako" )
@web.expose
#def history( self, trans, as_xml=False, show_deleted=False, show_hidden=False, hda_id=None, **kwd ):
def history( self, trans, as_xml=False, show_deleted=None, show_hidden=None, hda_id=None, **kwd ):
"""
Display the current history, creating a new history if necessary.
+1 -1
View File
@@ -288,7 +288,7 @@ var PersistantStorage = function( storageKey, storageDefaults ){
},
/** String representation.
*/
toString : function(){ return 'PersistantStorage(' + data + ')'; }
toString : function(){ return 'PersistantStorage(' + storageKey + ')'; }
});
return returnedStorage;
+3
View File
@@ -28,6 +28,7 @@ var HDABaseView = BaseView.extend( LoggableMixin ).extend(
* @see Backbone.View#initialize
*/
initialize : function( attributes ){
if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; }
this.log( this + '.initialize:', attributes );
/** list of rendering functions for the default, primary icon-buttons. */
@@ -334,6 +335,8 @@ var HDABaseView = BaseView.extend( LoggableMixin ).extend(
//TODO: not a fan of this dispatch
switch( this.model.get( 'state' ) ){
case HistoryDatasetAssociation.STATES.NEW :
break;
case HistoryDatasetAssociation.STATES.NOT_VIEWABLE :
this._render_body_not_viewable( body );
break;
+1 -2
View File
@@ -130,8 +130,7 @@ var HistoryDatasetAssociation = BaseModel.extend( LoggableMixin ).extend(
//TODO: to list inclusion test
//TODO: class level readyStates list
return (
( state === HistoryDatasetAssociation.STATES.NEW )
|| ( state === HistoryDatasetAssociation.STATES.OK )
( state === HistoryDatasetAssociation.STATES.OK )
|| ( state === HistoryDatasetAssociation.STATES.EMPTY )
|| ( state === HistoryDatasetAssociation.STATES.FAILED_METADATA )
|| ( state === HistoryDatasetAssociation.STATES.NOT_VIEWABLE )
+10 -3
View File
@@ -140,6 +140,8 @@ var HistoryPanel = BaseView.extend( LoggableMixin ).extend(
* @see Backbone.View#initialize
*/
initialize : function( attributes ){
// set the logger if requested
if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; }
this.log( this + '.initialize:', attributes );
// set up url templates
@@ -203,7 +205,7 @@ var HistoryPanel = BaseView.extend( LoggableMixin ).extend(
show_deleted : false,
show_hidden : false
});
this.log( 'this.storage:', this.storage.get() );
this.log( this + ' (prev) storage:', JSON.stringify( this.storage.get(), null, 2 ) );
// expanded Hdas is a map of hda.ids -> a boolean rep'ing whether this hda's body is expanded
// store any pre-expanded ids passed in
@@ -226,7 +228,7 @@ var HistoryPanel = BaseView.extend( LoggableMixin ).extend(
this.show_deleted = this.storage.get( 'show_deleted' );
this.show_hidden = this.storage.get( 'show_hidden' );
//this.log( 'this.show_deleted:', this.show_deleted, 'show_hidden', this.show_hidden );
this.log( '(init\'d) this.storage:', this.storage.get() );
this.log( this + ' (init\'d) storage:', this.storage.get() );
},
/** Add an hda to this history's collection
@@ -351,7 +353,8 @@ var HistoryPanel = BaseView.extend( LoggableMixin ).extend(
historyView.hdaViews[ hdaId ] = new historyView.HDAView({
model : hda,
expanded : expanded,
urlTemplates : historyView.hdaUrlTemplates
urlTemplates : historyView.hdaUrlTemplates,
logger : historyView.logger
});
historyView._setUpHdaListeners( historyView.hdaViews[ hdaId ] );
@@ -432,19 +435,23 @@ var HistoryPanel = BaseView.extend( LoggableMixin ).extend(
/** Handle the user toggling the deleted visibility by:
* (1) storing the new value in the persistant storage
* (2) re-rendering the history
* @returns {Boolean} new show_deleted setting
*/
toggleShowDeleted : function(){
this.storage.set( 'show_deleted', !this.storage.get( 'show_deleted' ) );
this.render();
return this.storage.get( 'show_deleted' );
},
/** Handle the user toggling the deleted visibility by:
* (1) storing the new value in the persistant storage
* (2) re-rendering the history
* @returns {Boolean} new show_hidden setting
*/
toggleShowHidden : function(){
this.storage.set( 'show_hidden', !this.storage.get( 'show_hidden' ) );
this.render();
return this.storage.get( 'show_hidden' );
},
/** Collapse all hda bodies and clear expandedHdas in the storage
+305 -1
View File
@@ -76,11 +76,11 @@ IconButtonView.templates = {
iconButton : Handlebars.partials.iconButton
};
var IconButtonCollection = Backbone.Collection.extend({
model: IconButton
});
//------------------------------------------------------------------------------
/**
* Menu with multiple icon buttons. Views are not needed nor used for individual buttons.
@@ -166,15 +166,319 @@ var GalaxyPaths = Backbone.Model.extend({
});
// =============================================================================
/** @class View for a popup menu
* @name PopupMenu
*
* @constructs
*/
var PopupMenu = Backbone.View.extend(
/** @lends PopupMenu.prototype */{
/* TODO:
add submenus
add hrefs
test various html keys
add make_popupmenus style
get template inside this file somehow
*/
/** Cache the desired button element and options, set up the button click handler
* NOTE: attaches this view as HTML/jQ data on the button for later use.
*/
//TODO: include docs on special option keys (divider, checked, etc.)
initialize : function( $button, options ){
// default settings
this.$button = $button || $( '<div/>' );
this.options = options || [];
//console.debug( this + '.initialize, button:', $button, ', options:', options );
// set up button click -> open menu behavior
var menu = this;
this.$button.click( function( event ){
menu._renderAndShow( event );
//event.stopPropagation();
return false;
});
// attach this view as a data object on the button - for later access
//TODO:?? memleak?
this.$button.data( 'PopupMenu', this );
// template loading is problematic - ui is loaded in base.mako
// and the template (prev.) needed to be loaded before ui
if( !this.templates.wrapper ){
this.templates.wrapper = Handlebars.templates[ 'template-popupmenu-wrapper' ];
}
},
/** Render the menu. NOTE: doesn't attach itself to the DOM.
* @see PopupMenu#_renderAndShow
*/
render : function(){
var menu = this;
// render the menu body
this.$el.addClass( 'popmenu-wrapper' )
.css({
position: 'absolute',
display: 'none'
});
//BUG: anchors within a.popupmenu-option render OUTSIDE the a.popupmenu-option!?
this.$el.html( PopupMenu.templates.menu({
options : this.options,
// sets menu div id to '{{ id }}-menu'
id : this.$button.attr( 'id' )
}));
//console.debug( this.$el, ':', this.$el.html() );
// set up behavior on each link/anchor elem
if( this.options.length ){
this.$el.find( 'li' ).each( function( i, li ){
var $li = $( li ),
$anchor = $li.children( 'a.popupmenu-option' ),
menuFunc = menu.options[ i ].func;
//console.debug( 'setting up behavior:', i, menu.options[ i ], $li, $anchor );
if( $anchor.length && menuFunc ){
$anchor.click( function( event ){
menuFunc( event, menu.options[ i ] );
});
}
// cache the anchor as a jq obj within the options obj
menu.options[ i ].$li = $li;
});
}
return this;
},
/** Get the absolute position/offset for the menu
*/
_getShownPosition : function( clickEvent ){
var menuWidth = this.$el.width(),
// display menu horiz. centered on click...
x = clickEvent.pageX - menuWidth / 2 ;
// ...but adjust that to handle horiz. scroll and window dimensions (draw entirely on visible screen area)
x = Math.min( x, $( document ).scrollLeft() + $( window ).width() - menuWidth - 5 );
x = Math.max( x, $( document ).scrollLeft() + 5 );
return {
top: clickEvent.pageY,
left: x
};
},
/** Render the menu, append to the page body at the click position, and set up the 'click-away' handlers, show
*/
_renderAndShow : function( clickEvent ){
this.render();
this.$el.appendTo( 'body' );
this.$el.css( this._getShownPosition( clickEvent ) );
this._setUpCloseBehavior();
this.$el.show();
},
/** Bind an event handler to all available frames so that when anything is clicked
* * the menu is removed from the DOM
* * The event handler unbinds itself
*/
_setUpCloseBehavior : function(){
var menu = this,
// function to close popup and unbind itself
closePopupWhenClicked = function( $elClicked ){
$elClicked.bind( "click.close_popup", function(){
menu.remove();
$elClicked.unbind( "click.close_popup" );
});
};
// bind to current, parent, and sibling frames
//TODO: (Assuming for now that this is the best way to do this...)
closePopupWhenClicked( $( window.document ) );
closePopupWhenClicked( $( window.top.document ) );
_.each( window.top.frames, function( siblingFrame ){
closePopupWhenClicked( $( siblingFrame.document ) );
});
},
/** Add a menu option/item at the given index
*/
addItem : function( item, index ){
// append to end if no index
index = ( index >= 0 )?( index ):( this.options.length );
this.options.splice( index, 0, item );
return this;
},
/** Remove a menu option/item at the given index
*/
removeItem : function( index ){
if( index >=0 ){
this.options.splice( index, 1 );
}
return this;
},
/** Search for a menu option by it's html
*/
findIndexByHtml : function( html ){
for( var i=0; i<this.options.length; i++ ){
if( ( _.has( this.options[i], 'html' ) )
&& ( this.options[i].html === html ) ){
return i;
}
}
return null;
},
/** Search for a menu option by it's html
*/
findItemByHtml : function( html ){
return this.options[( this.findIndexByHtml( html ) )];
},
/** String representation. */
toString : function(){
return 'PopupMenu';
}
});
PopupMenu.templates = {
menu : Handlebars.templates[ 'template-popupmenu-menu' ]
};
// -----------------------------------------------------------------------------
// the following class functions are bridges from the original make_popupmenu and make_popup_menus
// to the newer backbone.js PopupMenu
/** Create a PopupMenu from simple map initial_options activated by clicking button_element.
* Converts initial_options to object array used by PopupMenu.
* @param {jQuery|DOMElement} button_element element which, when clicked, activates menu
* @param {Object} initial_options map of key -> values, where
* key is option text, value is fn to call when option is clicked
* @returns {PopupMenu} the PopupMenu created
*/
PopupMenu.make_popupmenu = function( button_element, initial_options ){
var convertedOptions = [];
_.each( initial_options, function( optionVal, optionKey ){
var newOption = { html: optionKey };
// keys with null values indicate: header
if( optionVal === null ){ // !optionVal? (null only?)
newOption.header = true;
// keys with function values indicate: a menu option
} else if( jQuery.type( optionVal ) === 'function' ){
newOption.func = optionVal;
}
//TODO:?? any other special optionVals?
// there was no divider option originally
convertedOptions.push( newOption );
});
return new PopupMenu( $( button_element ), convertedOptions );
};
/** Find all anchors in $parent (using selector) and covert anchors into a PopupMenu options map.
* @param {jQuery} $parent the element that contains the links to convert to options
* @param {String} selector jq selector string to find links
* @returns {Object[]} the options array to initialize a PopupMenu
*/
//TODO: lose parent and selector, pass in array of links, use map to return options
PopupMenu.convertLinksToOptions = function( $parent, selector ){
$parent = $( $parent );
selector = selector || 'a';
var options = [];
$parent.find( selector ).each( function( elem, i ){
var option = {},
$link = $( elem );
// convert link text to the option text (html) and the href into the option func
option.html = $link.text();
if( linkHref ){
var linkHref = $link.attr( 'href' ),
linkTarget = $link.attr( 'target' ),
confirmText = $link.attr( 'confirm' );
option.func = function(){
// if there's a "confirm" attribute, throw up a confirmation dialog, and
// if the user cancels - do nothing
if( ( confirmText ) && ( !confirm( confirmText ) ) ){ return; }
// if there's no confirm attribute, or the user accepted the confirm dialog:
var f;
switch( linkTarget ){
// relocate the center panel
case '_parent':
window.parent.location = linkHref;
break;
// relocate the entire window
case '_top':
window.top.location = linkHref;
break;
// Http request target is a window named demolocal on the local box
//TODO: I still don't understand this option (where the hell does f get set? confirm?)
case 'demo':
if( f === undefined || f.closed ){
f = window.open( linkHref, linkTarget );
f.creator = self;
}
break;
// relocate this panel
default:
window.location = linkHref;
}
};
}
options.push( option );
});
return options;
};
/** Create a single popupmenu from existing DOM button and anchor elements
* @param {jQuery} $buttonElement the element that when clicked will open the menu
* @param {jQuery} $menuElement the element that contains the anchors to convert into a menu
* @param {String} menuElementLinkSelector jq selector string used to find anchors to be made into menu options
* @returns {PopupMenu} the PopupMenu (Backbone View) that can render, control the menu
*/
PopupMenu.fromExistingDom = function( $buttonElement, $menuElement, menuElementLinkSelector ){
$buttonElement = $( $buttonElement );
$menuElement = $( $menuElement );
var options = PopupMenu.convertLinksToOptions( $menuElement, menuElementLinkSelector );
// we're done with the menu (having converted it to an options map)
$menuElement.remove();
return new PopupMenu( $buttonElement, options );
};
/** Create all popupmenus within a document or a more specific element
* @param {DOMElement} parent the DOM element in which to search for popupmenus to build (defaults to document)
* @param {String} menuSelector jq selector string to find popupmenu menu elements (defaults to "div[popupmenu]")
* @param {Function} buttonSelectorBuildFn the function to build the jq button selector.
* Will be passed $menuElement, parent.
* (Defaults to return '#' + $menuElement.attr( 'popupmenu' ); )
* @returns {PopupMenu[]} array of popupmenus created
*/
PopupMenu.make_popup_menus = function( parent, menuSelector, buttonSelectorBuildFn ){
parent = parent || document;
// orig. Glx popupmenu menus have a (non-std) attribute 'popupmenu'
// which contains the id of the button that activates the menu
menuSelector = menuSelector || 'div[popupmenu]';
// default to (orig. Glx) matching button to menu by using the popupmenu attr of the menu as the id of the button
buttonSelectorBuildFn = buttonSelectorBuildFn || function( $menuElement, parent ){
return '#' + $menuElement.attr( 'popupmenu' );
};
// aggregate and return all PopupMenus
var popupMenusCreated = [];
$( parent ).find( menuSelector ).each( function(){
var $menuElement = $( this ),
$buttonElement = $( parent ).find( buttonSelectorBuildFn( $menuElement, parent ) );
popupMenusCreated.push( PopupMenu.fromDom( $buttonElement, $menuElement ) );
$buttonElement.addClass( 'popup' );
});
return popupMenusCreated;
}
+1 -1
View File
@@ -1 +1 @@
var BaseModel=Backbone.RelationalModel.extend({defaults:{name:null,hidden:false},show:function(){this.set("hidden",false)},hide:function(){this.set("hidden",true)},is_visible:function(){return !this.attributes.hidden}});var BaseView=Backbone.View.extend({initialize:function(){this.model.on("change:hidden",this.update_visible,this);this.update_visible()},update_visible:function(){if(this.model.attributes.hidden){this.$el.hide()}else{this.$el.show()}}});var LoggableMixin={logger:null,log:function(){if(this.logger){return this.logger.log.apply(this.logger,arguments)}return undefined}};var GalaxyLocalization=jQuery.extend({},{ALIAS_NAME:"_l",localizedStrings:{},setLocalizedString:function(b,a){var c=this;var d=function(f,e){if(f!==e){c.localizedStrings[f]=e}};if(jQuery.type(b)==="string"){d(b,a)}else{if(jQuery.type(b)==="object"){jQuery.each(b,function(e,f){d(e,f)})}else{throw ("Localization.setLocalizedString needs either a string or object as the first argument, given: "+b)}}},localize:function(a){return this.localizedStrings[a]||a},toString:function(){return"GalaxyLocalization"}});window[GalaxyLocalization.ALIAS_NAME]=function(a){return GalaxyLocalization.localize(a)};var PersistantStorage=function(g,d){if(!g){throw ("PersistantStorage needs storageKey argument")}d=d||{};var b=jQuery.jStorage.get,c=jQuery.jStorage.set,a=jQuery.jStorage.deleteKey;function e(i,h){i=i||{};h=h||null;return{get:function(j){if(j===undefined){return i}else{if(i.hasOwnProperty(j)){return(jQuery.type(i[j])==="object")?(new e(i[j],this)):(i[j])}}return undefined},set:function(j,k){i[j]=k;this._save();return this},deleteKey:function(j){delete i[j];this._save();return this},_save:function(){return h._save()},toString:function(){return("StorageRecursionHelper("+i+")")}}}var f={};data=b(g);if(data===null){data=jQuery.extend(true,{},d);c(g,data)}f=new e(data);jQuery.extend(f,{_save:function(h){return c(g,f.get())},destroy:function(){return a(g)},toString:function(){return"PersistantStorage("+data+")"}});return f};
var BaseModel=Backbone.RelationalModel.extend({defaults:{name:null,hidden:false},show:function(){this.set("hidden",false)},hide:function(){this.set("hidden",true)},is_visible:function(){return !this.attributes.hidden}});var BaseView=Backbone.View.extend({initialize:function(){this.model.on("change:hidden",this.update_visible,this);this.update_visible()},update_visible:function(){if(this.model.attributes.hidden){this.$el.hide()}else{this.$el.show()}}});var LoggableMixin={logger:null,log:function(){if(this.logger){return this.logger.log.apply(this.logger,arguments)}return undefined}};var GalaxyLocalization=jQuery.extend({},{ALIAS_NAME:"_l",localizedStrings:{},setLocalizedString:function(b,a){var c=this;var d=function(f,e){if(f!==e){c.localizedStrings[f]=e}};if(jQuery.type(b)==="string"){d(b,a)}else{if(jQuery.type(b)==="object"){jQuery.each(b,function(e,f){d(e,f)})}else{throw ("Localization.setLocalizedString needs either a string or object as the first argument, given: "+b)}}},localize:function(a){return this.localizedStrings[a]||a},toString:function(){return"GalaxyLocalization"}});window[GalaxyLocalization.ALIAS_NAME]=function(a){return GalaxyLocalization.localize(a)};var PersistantStorage=function(g,d){if(!g){throw ("PersistantStorage needs storageKey argument")}d=d||{};var b=jQuery.jStorage.get,c=jQuery.jStorage.set,a=jQuery.jStorage.deleteKey;function e(i,h){i=i||{};h=h||null;return{get:function(j){if(j===undefined){return i}else{if(i.hasOwnProperty(j)){return(jQuery.type(i[j])==="object")?(new e(i[j],this)):(i[j])}}return undefined},set:function(j,k){i[j]=k;this._save();return this},deleteKey:function(j){delete i[j];this._save();return this},_save:function(){return h._save()},toString:function(){return("StorageRecursionHelper("+i+")")}}}var f={};data=b(g);if(data===null){data=jQuery.extend(true,{},d);c(g,data)}f=new e(data);jQuery.extend(f,{_save:function(h){return c(g,f.get())},destroy:function(){return a(g)},toString:function(){return"PersistantStorage("+g+")"}});return f};
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
var HistoryDatasetAssociation=BaseModel.extend(LoggableMixin).extend({defaults:{history_id:null,model_class:"HistoryDatasetAssociation",hid:0,id:null,name:"(unnamed dataset)",state:"ok",data_type:null,file_size:0,file_ext:"",meta_files:[],misc_blurb:"",misc_info:"",deleted:false,purged:false,visible:false,accessible:true},urlRoot:"api/histories/",url:function(){return"api/histories/"+this.get("history_id")+"/contents/"+this.get("id")},initialize:function(){this.log(this+".initialize",this.attributes);this.log("\tparent history_id: "+this.get("history_id"));if(!this.get("accessible")){this.set("state",HistoryDatasetAssociation.STATES.NOT_VIEWABLE)}this.on("change:state",function(b,a){this.log(this+" has changed state:",b,a);if(this.inReadyState()){this.trigger("state:ready",this.get("id"),a,this.previous("state"),b)}})},isDeletedOrPurged:function(){return(this.get("deleted")||this.get("purged"))},isVisible:function(b,c){var a=true;if((!b)&&(this.get("deleted")||this.get("purged"))){a=false}if((!c)&&(!this.get("visible"))){a=false}return a},inReadyState:function(){var a=this.get("state");return((a===HistoryDatasetAssociation.STATES.NEW)||(a===HistoryDatasetAssociation.STATES.OK)||(a===HistoryDatasetAssociation.STATES.EMPTY)||(a===HistoryDatasetAssociation.STATES.FAILED_METADATA)||(a===HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(a===HistoryDatasetAssociation.STATES.DISCARDED)||(a===HistoryDatasetAssociation.STATES.ERROR))},hasData:function(){return(this.get("file_size")>0)},toString:function(){var a=this.get("id")||"";if(this.get("name")){a+=':"'+this.get("name")+'"'}return"HistoryDatasetAssociation("+a+")"}});HistoryDatasetAssociation.STATES={UPLOAD:"upload",QUEUED:"queued",PAUSED:"paused",RUNNING:"running",SETTING_METADATA:"setting_metadata",NEW:"new",EMPTY:"empty",OK:"ok",FAILED_METADATA:"failed_metadata",NOT_VIEWABLE:"noPermission",DISCARDED:"discarded",ERROR:"error"};var HDACollection=Backbone.Collection.extend(LoggableMixin).extend({model:HistoryDatasetAssociation,initialize:function(){},ids:function(){return this.map(function(a){return a.id})},hidToCollectionIndex:function(a){if(!a){return this.models.length}var d=this.models.length-1;for(var b=d;b>=0;b--){var c=this.at(b).get("hid");if(c==a){return b}if(c<a){return b+1}}return null},getVisible:function(a,b){return this.filter(function(c){return c.isVisible(a,b)})},getStateLists:function(){var a={};_.each(_.values(HistoryDatasetAssociation.STATES),function(b){a[b]=[]});this.each(function(b){a[b.get("state")].push(b.get("id"))});return a},running:function(){var a=[];this.each(function(b){if(!b.inReadyState()){a.push(b.get("id"))}});return a},update:function(a){this.log(this+"update:",a);if(!(a&&a.length)){return[]}var c=this,b=null;_.each(a,function(f,d){var e=c.get(f);if(e){e.fetch();b.push(e)}});return b},toString:function(){return("HDACollection()")}});
var HistoryDatasetAssociation=BaseModel.extend(LoggableMixin).extend({defaults:{history_id:null,model_class:"HistoryDatasetAssociation",hid:0,id:null,name:"(unnamed dataset)",state:"ok",data_type:null,file_size:0,file_ext:"",meta_files:[],misc_blurb:"",misc_info:"",deleted:false,purged:false,visible:false,accessible:true},urlRoot:"api/histories/",url:function(){return"api/histories/"+this.get("history_id")+"/contents/"+this.get("id")},initialize:function(){this.log(this+".initialize",this.attributes);this.log("\tparent history_id: "+this.get("history_id"));if(!this.get("accessible")){this.set("state",HistoryDatasetAssociation.STATES.NOT_VIEWABLE)}this.on("change:state",function(b,a){this.log(this+" has changed state:",b,a);if(this.inReadyState()){this.trigger("state:ready",this.get("id"),a,this.previous("state"),b)}})},isDeletedOrPurged:function(){return(this.get("deleted")||this.get("purged"))},isVisible:function(b,c){var a=true;if((!b)&&(this.get("deleted")||this.get("purged"))){a=false}if((!c)&&(!this.get("visible"))){a=false}return a},inReadyState:function(){var a=this.get("state");return((a===HistoryDatasetAssociation.STATES.OK)||(a===HistoryDatasetAssociation.STATES.EMPTY)||(a===HistoryDatasetAssociation.STATES.FAILED_METADATA)||(a===HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(a===HistoryDatasetAssociation.STATES.DISCARDED)||(a===HistoryDatasetAssociation.STATES.ERROR))},hasData:function(){return(this.get("file_size")>0)},toString:function(){var a=this.get("id")||"";if(this.get("name")){a+=':"'+this.get("name")+'"'}return"HistoryDatasetAssociation("+a+")"}});HistoryDatasetAssociation.STATES={UPLOAD:"upload",QUEUED:"queued",PAUSED:"paused",RUNNING:"running",SETTING_METADATA:"setting_metadata",NEW:"new",EMPTY:"empty",OK:"ok",FAILED_METADATA:"failed_metadata",NOT_VIEWABLE:"noPermission",DISCARDED:"discarded",ERROR:"error"};var HDACollection=Backbone.Collection.extend(LoggableMixin).extend({model:HistoryDatasetAssociation,initialize:function(){},ids:function(){return this.map(function(a){return a.id})},hidToCollectionIndex:function(a){if(!a){return this.models.length}var d=this.models.length-1;for(var b=d;b>=0;b--){var c=this.at(b).get("hid");if(c==a){return b}if(c<a){return b+1}}return null},getVisible:function(a,b){return this.filter(function(c){return c.isVisible(a,b)})},getStateLists:function(){var a={};_.each(_.values(HistoryDatasetAssociation.STATES),function(b){a[b]=[]});this.each(function(b){a[b.get("state")].push(b.get("id"))});return a},running:function(){var a=[];this.each(function(b){if(!b.inReadyState()){a.push(b.get("id"))}});return a},update:function(a){this.log(this+"update:",a);if(!(a&&a.length)){return[]}var c=this,b=null;_.each(a,function(f,d){var e=c.get(f);if(e){e.fetch();b.push(e)}});return b},toString:function(){return("HDACollection()")}});
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-popupmenu-menu"]=b(function(f,s,q,l,x){q=q||f.helpers;var r="",i,h,d="function",c=this.escapeExpression,p=this;function o(B,A){var y="",z;y+="\n ";z=B.options;z=q.each.call(B,z,{hash:{},inverse:p.noop,fn:p.program(2,n,A)});if(z||z===0){y+=z}y+="\n";return y}function n(B,A){var y="",z;y+="\n ";z=B.divider;z=q["if"].call(B,z,{hash:{},inverse:p.program(5,k,A),fn:p.program(3,m,A)});if(z||z===0){y+=z}y+="\n ";return y}function m(z,y){return'\n <li class="divider"></li>\n '}function k(B,A){var y="",z;y+="\n ";z=B.header;z=q["if"].call(B,z,{hash:{},inverse:p.program(8,g,A),fn:p.program(6,j,A)});if(z||z===0){y+=z}y+="\n ";return y}function j(C,B){var z="",A,y;z+='\n <li class="head"><a href=""javascript:void(0);">';y=q.html;if(y){A=y.call(C,{hash:{}})}else{A=C.html;A=typeof A===d?A():A}if(A||A===0){z+=A}z+="</a></li>\n ";return z}function g(C,B){var z="",A,y;z+="\n ";z+='\n <li><a href="';A=C.href;A=q["if"].call(C,A,{hash:{},inverse:p.program(11,w,B),fn:p.program(9,e,B)});if(A||A===0){z+=A}z+='"\n ';A=C.href;A=q["if"].call(C,A,{hash:{},inverse:p.noop,fn:p.program(13,v,B)});if(A||A===0){z+=A}z+=' class="popupmenu-option">\n ';A=C.checked;A=q["if"].call(C,A,{hash:{},inverse:p.noop,fn:p.program(15,u,B)});if(A||A===0){z+=A}z+="\n ";y=q.html;if(y){A=y.call(C,{hash:{}})}else{A=C.html;A=typeof A===d?A():A}if(A||A===0){z+=A}z+="\n </a></li>\n ";return z}function e(B,A){var z,y;y=q.href;if(y){z=y.call(B,{hash:{}})}else{z=B.href;z=typeof z===d?z():z}return c(z)}function w(z,y){return"javascript:void(0);"}function v(C,B){var z="",A,y;z+='target="';y=q.target;if(y){A=y.call(C,{hash:{}})}else{A=C.target;A=typeof A===d?A():A}z+=c(A)+'"';return z}function u(z,y){return'<span class="ficon ok"></span>'}function t(z,y){return"\n <li>No Options.</li>\n"}r+='<ul id="';h=q.id;if(h){i=h.call(s,{hash:{}})}else{i=s.id;i=typeof i===d?i():i}r+=c(i)+'-menu" class="dropdown-menu">\n';i=s.options;i=i==null||i===false?i:i.length;i=q["if"].call(s,i,{hash:{},inverse:p.program(17,t,x),fn:p.program(1,o,x)});if(i||i===0){r+=i}r+="\n</ul>";return r})})();
@@ -0,0 +1,117 @@
(function() {
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
templates['template-popupmenu-menu'] = template(function (Handlebars,depth0,helpers,partials,data) {
helpers = helpers || Handlebars.helpers;
var buffer = "", stack1, foundHelper, functionType="function", escapeExpression=this.escapeExpression, self=this;
function program1(depth0,data) {
var buffer = "", stack1;
buffer += "\n ";
stack1 = depth0.options;
stack1 = helpers.each.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(2, program2, data)});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\n";
return buffer;}
function program2(depth0,data) {
var buffer = "", stack1;
buffer += "\n ";
stack1 = depth0.divider;
stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.program(5, program5, data),fn:self.program(3, program3, data)});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\n ";
return buffer;}
function program3(depth0,data) {
return "\n <li class=\"divider\"></li>\n ";}
function program5(depth0,data) {
var buffer = "", stack1;
buffer += "\n ";
stack1 = depth0.header;
stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.program(8, program8, data),fn:self.program(6, program6, data)});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\n ";
return buffer;}
function program6(depth0,data) {
var buffer = "", stack1, foundHelper;
buffer += "\n <li class=\"head\"><a href=\"\"javascript:void(0);\">";
foundHelper = helpers.html;
if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
else { stack1 = depth0.html; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "</a></li>\n ";
return buffer;}
function program8(depth0,data) {
var buffer = "", stack1, foundHelper;
buffer += "\n ";
buffer += "\n <li><a href=\"";
stack1 = depth0.href;
stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.program(11, program11, data),fn:self.program(9, program9, data)});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\"\n ";
stack1 = depth0.href;
stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(13, program13, data)});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += " class=\"popupmenu-option\">\n ";
stack1 = depth0.checked;
stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(15, program15, data)});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\n ";
foundHelper = helpers.html;
if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
else { stack1 = depth0.html; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\n </a></li>\n ";
return buffer;}
function program9(depth0,data) {
var stack1, foundHelper;
foundHelper = helpers.href;
if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
else { stack1 = depth0.href; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
return escapeExpression(stack1);}
function program11(depth0,data) {
return "javascript:void(0);";}
function program13(depth0,data) {
var buffer = "", stack1, foundHelper;
buffer += "target=\"";
foundHelper = helpers.target;
if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
else { stack1 = depth0.target; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
buffer += escapeExpression(stack1) + "\"";
return buffer;}
function program15(depth0,data) {
return "<span class=\"ficon ok\"></span>";}
function program17(depth0,data) {
return "\n <li>No Options.</li>\n";}
buffer += "<ul id=\"";
foundHelper = helpers.id;
if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); }
else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1() : stack1; }
buffer += escapeExpression(stack1) + "-menu\" class=\"dropdown-menu\">\n";
stack1 = depth0.options;
stack1 = stack1 == null || stack1 === false ? stack1 : stack1.length;
stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.program(17, program17, data),fn:self.program(1, program1, data)});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\n</ul>";
return buffer;});
})();
+27 -2
View File
@@ -1,4 +1,4 @@
<script type="text/template" class="template-history" id="template-user-quotaMeter-quota">
<script type="text/template" class="template-user" id="template-user-quotaMeter-quota">
<div id="quota-meter" class="quota-meter progress">
<div id="quota-meter-bar" class="quota-meter-bar bar" style="width: {{quota_percent}}%"></div>
{{! TODO: remove the hardcoded style }}
@@ -8,7 +8,7 @@
</div>
</script>
<script type="text/template" class="template-history" id="template-user-quotaMeter-usage">
<script type="text/template" class="template-user" id="template-user-quotaMeter-usage">
{{! TODO: remove the hardcoded styles }}
<div id="quota-meter" class="quota-meter" style="background-color: transparent">
<div id="quota-meter-text" class="quota-meter-text" style="top: 6px; color: white">
@@ -16,3 +16,28 @@
</div>
</div>
</script>
<script type="text/template" class="template-ui" id="template-popupmenu-menu">
<ul id="{{ id }}-menu" class="dropdown-menu">
{{#if options.length }}
{{#each options }}
{{#if divider }}
<li class="divider"></li>
{{else}}
{{#if header }}
<li class="head"><a href=""javascript:void(0);">{{{ html }}}</a></li>
{{else}}
{{!NOTE: changed from # to void fn }}
<li><a href="{{#if href }}{{ href }}{{else}}javascript:void(0);{{/if}}"
{{#if href }}target="{{ target }}"{{/if}} class="popupmenu-option">
{{#if checked }}<span class="ficon ok"></span>{{/if}}
{{{ html }}}
</a></li>
{{/if}}
{{/if}}
{{/each}}
{{else}}
<li>No Options.</li>
{{/if}}
</ul>
</script>
+8 -1
View File
@@ -31,11 +31,18 @@
"libs/jquery/jquery",
"libs/json2",
"libs/bootstrap",
"galaxy.base",
"libs/underscore",
"libs/backbone/backbone",
"libs/backbone/backbone-relational",
"libs/handlebars.runtime",
"galaxy.base"
)}
${h.templates(
"template-popupmenu-menu"
)}
${h.js(
"mvc/ui"
)}
+9 -1
View File
@@ -56,9 +56,17 @@
'libs/backbone/backbone',
'libs/backbone/backbone-relational',
'libs/handlebars.runtime',
'mvc/ui',
'galaxy.base'
)}
${h.templates(
"template-popupmenu-menu"
)}
${h.js(
"mvc/ui"
)}
<script type="text/javascript">
// console protection
window.console = window.console || {
+89 -14
View File
@@ -223,10 +223,12 @@ ${parent.javascripts()}
${h.js(
"libs/jquery/jstorage",
"libs/jquery/jquery.autocomplete", "galaxy.autocom_tagging",
"libs/json2",
##"libs/json2",
##"libs/bootstrap",
"libs/backbone/backbone-relational",
"mvc/base-mvc", "mvc/ui"
## I think we can remove some of these
##"libs/backbone/backbone-relational",
"mvc/base-mvc",
##"mvc/ui"
)}
${h.templates(
@@ -302,13 +304,9 @@ $(function(){
galaxyPageSetUp();
Galaxy.historyFrame = window;
// ostensibly, this is the App
//if( window.console && console.debug ){
// //if( console.clear ){ console.clear(); }
// console.pretty = function( o ){ $( '<pre/>' ).text( JSON.stringify( o, null, ' ' ) ).appendTo( 'body' ); }
// top.storage = jQuery.jStorage
//}
var debugging = ( new PersistantStorage( '__history_panel' ).get( 'debugging' ) || false );
// ostensibly, this is the App
// LOAD INITIAL DATA IN THIS PAGE - since we're already sending it...
// ...use mako to 'bootstrap' the models
var user = ${ get_current_user() },
@@ -325,7 +323,7 @@ $(function(){
var historyPanel = new HistoryPanel({
model : new History( history, hdas ),
urlTemplates : galaxy_paths.attributes,
//logger : console,
logger : ( debugging )?( console ):( null ),
// is page sending in show settings? if so override history's
show_deleted : ${ 'true' if show_deleted == True else ( 'null' if show_deleted == None else 'false' ) },
show_hidden : ${ 'true' if show_hidden == True else ( 'null' if show_hidden == None else 'false' ) }
@@ -336,13 +334,17 @@ $(function(){
//historyPanel = new HistoryPanel({
// model: new History(),
// urlTemplates : galaxy_paths.attributes,
// logger : console,
// logger : ( debugging )?( console ):( null ),
// // is page sending in show settings? if so override history's
// show_deleted : ${ 'true' if show_deleted == True else ( 'null' if show_deleted == None else 'false' ) },
// show_hidden : ${ 'true' if show_hidden == True else ( 'null' if show_hidden == None else 'false' ) }
//});
//historyPanel.model.loadFromApi( history.id );
// set it up to be accessible across iframes
//TODO:?? mem leak
top.Galaxy.currHistoryPanel = historyPanel;
// QUOTA METER is a cross-frame ui element (meter in masthead, over quota message in history)
// create it and join them here for now (via events)
@@ -352,6 +354,7 @@ $(function(){
//window.currUser.logger = console;
var quotaMeter = new UserQuotaMeter({
model : currUser,
//logger : ( debugging )?( console ):( null ),
el : $( top.document ).find( '.quota-meter-container' )
});
//quotaMeter.logger = console; window.quotaMeter = quotaMeter
@@ -373,9 +376,81 @@ $(function(){
quotaMeter.update()
}, quotaMeter );
// set it up to be accessible across iframes
//TODO:?? mem leak
top.Galaxy.currHistoryPanel = historyPanel;
//ANOTHER cross-frame element is the history-options-button...
// in this case, we need to change the popupmenu options listed to include some functions for this history
// these include: current (1 & 2) 'show/hide' delete and hidden functions, and (3) the collapse all option
(function(){
// don't try this if the history panel is in it's own window
if( top.document === window.document ){
return;
}
// lots of wtf here...due to infernalframes
var $historyButtonWindow = $( top.document ),
HISTORY_MENU_BUTTON_ID = 'history-options-button',
$historyMenuButton = $historyButtonWindow.find( '#' + HISTORY_MENU_BUTTON_ID ),
// jq data in another frame can only be accessed by the jQuery in that frame,
// get the jQuery from the top frame (that contains the history-options-button)
START_INSERTING_AT_INDEX = 11,
COLLAPSE_OPTION_TEXT = _l("Collapse Expanded Datasets"),
DELETED_OPTION_TEXT = _l("Include Deleted Datasets"),
HIDDEN_OPTION_TEXT = _l("Include Hidden Datasets");
windowJQ = $( top )[0].jQuery,
popupMenu = ( windowJQ && $historyMenuButton[0] )?( windowJQ.data( $historyMenuButton[0], 'PopupMenu' ) )
:( null );
//console.debug(
// '$historyButtonWindow:', $historyButtonWindow,
// '$historyMenuButton:', $historyMenuButton,
// 'windowJQ:', windowJQ,
// 'popupmenu:', popupMenu
//);
if( !popupMenu ){ return; }
// since the history frame reloads so often (compared to the main window),
// we need to check whether these options are there already before we add them again
//NOTE: we use the global here because these remain bound in the main window even if panel refreshes
//TODO: too much boilerplate
if( popupMenu.findIndexByHtml( COLLAPSE_OPTION_TEXT ) === null ){
popupMenu.addItem({
html : COLLAPSE_OPTION_TEXT,
func : function() {
Galaxy.currHistoryPanel.collapseAllHdaBodies();
}
}, START_INSERTING_AT_INDEX )
}
var deletedOptionIndex = popupMenu.findIndexByHtml( DELETED_OPTION_TEXT );
if( deletedOptionIndex === null ){
popupMenu.addItem({
html : DELETED_OPTION_TEXT,
func : function( clickEvent, thisMenuOption ){
var show_deleted = Galaxy.currHistoryPanel.toggleShowDeleted();
thisMenuOption.checked = show_deleted;
}
}, START_INSERTING_AT_INDEX + 1 )
deletedOptionIndex = START_INSERTING_AT_INDEX + 1;
}
// whether was there or added, update the checked option to reflect the panel's settings on the panel render
popupMenu.options[ deletedOptionIndex ].checked = Galaxy.currHistoryPanel.storage.get( 'show_deleted' );
var hiddenOptionIndex = popupMenu.findIndexByHtml( HIDDEN_OPTION_TEXT );
if( hiddenOptionIndex === null ){
popupMenu.addItem({
html : HIDDEN_OPTION_TEXT,
func : function( clickEvent, thisMenuOption ){
var show_hidden = Galaxy.currHistoryPanel.toggleShowHidden();
thisMenuOption.checked = show_hidden;
}
}, START_INSERTING_AT_INDEX + 2 )
hiddenOptionIndex = START_INSERTING_AT_INDEX + 2;
}
// whether was there or added, update the checked option to reflect the panel's settings on the panel render
popupMenu.options[ hiddenOptionIndex ].checked = Galaxy.currHistoryPanel.storage.get( 'show_hidden' );
})();
//TODO: both the quota meter and the options-menu stuff need to be moved out when iframes are removed
return;
});
+1 -14
View File
@@ -10,7 +10,7 @@
$(function(){
// Init history options.
$("#history-options-button").css( "position", "relative" );
make_popupmenu( $("#history-options-button"), {
var popupmenu = PopupMenu.make_popupmenu( $("#history-options-button"), {
"${_("History Lists")}": null,
"${_("Saved Histories")}": function() {
galaxy_main.location = "${h.url_for( controller='history', action='list')}";
@@ -40,18 +40,6 @@
"${_("Resume Paused Jobs")}": function() {
galaxy_history.location = "${h.url_for( controller='history', action='resume_paused_jobs', current=True)}";
},
"${_("Collapse Expanded Datasets")}": function() {
Galaxy.currHistoryPanel.collapseAllHdaBodies();
//galaxy_history.location = "${h.url_for( controller='root', action='history', show_deleted=True)}";
},
"${_("Show/Hide Deleted Datasets")}": function() {
Galaxy.currHistoryPanel.toggleShowDeleted();
//galaxy_history.location = "${h.url_for( controller='root', action='history', show_deleted=True)}";
},
"${_("Show/Hide Hidden Datasets")}": function() {
Galaxy.currHistoryPanel.toggleShowHidden();
//galaxy_history.location = "${h.url_for( controller='root', action='history', show_hidden=True)}";
},
"${_("Unhide Hidden Datasets")}": function() {
if ( confirm( "Really unhide all hidden datasets?" ) ) {
galaxy_main.location = "${h.url_for( controller='history', action='unhide_datasets', current=True )}";
@@ -83,7 +71,6 @@
galaxy_main.location = "${h.url_for( controller='history', action='import_archive' )}";
}
});
});
</script>
</%def>
+24 -13
View File
@@ -14,11 +14,12 @@ class TestHistory( TwillTestCase ):
name = 'anonymous'
self.new_history( name=name )
global anonymous_history
anonymous_history = sa_session.query( galaxy.model.History ) \
.filter( and_( galaxy.model.History.table.c.deleted==False,
galaxy.model.History.table.c.name==name ) ) \
.order_by( desc( galaxy.model.History.table.c.create_time ) ) \
.first()
anonymous_history = (
sa_session.query( galaxy.model.History )
.filter( and_( galaxy.model.History.table.c.deleted==False, galaxy.model.History.table.c.name==name ) )
.order_by( desc( galaxy.model.History.table.c.create_time ) )
.first()
)
assert anonymous_history is not None, "Problem retrieving anonymous_history from database"
# Upload a dataset to anonymous_history so it will be set as the current history after login
self.upload_file( '1.bed', dbkey='hg18' )
@@ -235,27 +236,35 @@ class TestHistory( TwillTestCase ):
# Check list of histories to make sure shared history3 was cloned
strings_displayed=[ "Clone of '%s' shared by '%s'" % ( history3.name, admin_user.email ) ]
self.view_stored_active_histories( strings_displayed=strings_displayed )
def test_035_clone_current_history( self ):
"""Testing cloning the current history"""
# logged in as regular_user1
self.logout()
self.login( email=admin_user.email )
# Current history should be history3, add more datasets to history3, then delete them so we can
# test cloning activatable datasets as well as only the active datasets
self.upload_file( '2.bed', dbkey='hg18' )
hda_2_bed = sa_session.query( galaxy.model.HistoryDatasetAssociation ) \
.filter( and_( galaxy.model.HistoryDatasetAssociation.table.c.history_id==history3.id,
galaxy.model.HistoryDatasetAssociation.table.c.name=='2.bed' ) ) \
.first()
hda_2_bed = (
sa_session.query( galaxy.model.HistoryDatasetAssociation )
.filter( and_( galaxy.model.HistoryDatasetAssociation.table.c.history_id==history3.id,
galaxy.model.HistoryDatasetAssociation.table.c.name=='2.bed' ) )
.first()
)
assert hda_2_bed is not None, "Problem retrieving hda_2_bed from database"
self.delete_history_item( str( hda_2_bed.id ) )
self.upload_file( '3.bed', dbkey='hg18' )
hda_3_bed = sa_session.query( galaxy.model.HistoryDatasetAssociation ) \
.filter( and_( galaxy.model.HistoryDatasetAssociation.table.c.history_id==history3.id,
galaxy.model.HistoryDatasetAssociation.table.c.name=='3.bed' ) ) \
.first()
hda_3_bed = (
sa_session.query( galaxy.model.HistoryDatasetAssociation )
.filter( and_( galaxy.model.HistoryDatasetAssociation.table.c.history_id==history3.id,
galaxy.model.HistoryDatasetAssociation.table.c.name=='3.bed' ) )
.first()
)
assert hda_3_bed is not None, "Problem retrieving hda_3_bed from database"
self.delete_history_item( str( hda_3_bed.id ) )
# Test cloning activatable datasets
self.clone_history( self.security.encode_id( history3.id ),
'activatable',
@@ -267,6 +276,7 @@ class TestHistory( TwillTestCase ):
.order_by( desc( galaxy.model.History.table.c.create_time ) ) \
.first()
assert history3_clone2 is not None, "Problem retrieving history3_clone2 from database"
# Check list of histories to make sure shared history3 was cloned
self.view_stored_active_histories( strings_displayed=[ "Clone of '%s'" % history3.name ] )
# Switch to the cloned history to make sure activatable datasets were cloned
@@ -307,6 +317,7 @@ class TestHistory( TwillTestCase ):
raise AssertionError, "Deleted datasets incorrectly included in cloned history history3_clone3"
except:
pass
def test_040_sharing_mulitple_histories_with_multiple_users( self ):
"""Testing sharing multiple histories containing only public datasets with multiple users"""
# Logged in as admin_user