UI, history multi-view: simplify header UI, label current history, some clean up

This commit is contained in:
Carl Eberhard
2015-02-12 10:06:37 -05:00
parent de1cd01954
commit eea2a8a346
4 changed files with 336 additions and 436 deletions
+146 -210
View File
@@ -6,7 +6,6 @@ define([
"ui/mode-button",
"ui/search-input"
], function( HISTORY_MODEL, HPANEL_EDIT, baseMVC, ajaxQueue ){
window.HISTORY_MODEL = HISTORY_MODEL;
//==============================================================================
/** */
function historyCopyDialog( history, options ){
@@ -265,7 +264,7 @@ var HistoryPanelColumn = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
return ( this.currentHistory )?
[
'<div class="pull-left">',
'<button class="create-new btn btn-default">', _l( 'Create new' ), '</button> ',
'<strong class="current-label">', _l( 'Current History' ), '</strong>',
'</div>'
].join( '' )
:[
@@ -328,6 +327,7 @@ var HistoryPanelColumn = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
//logger : console,
className : 'multi-history-columns',
// ------------------------------------------------------------------------ set up
/** Set up internals, history collection, and columns to display the history */
@@ -366,9 +366,8 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
/** model id to column map */
this.columnMap = {};
//TODO: why create here?
//TODO: why create here?
this.createColumns( options.columnOptions );
this.setUpListeners();
},
@@ -386,10 +385,7 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
var multipanel = this;
multipanel.stopListening( multipanel.collection );
multipanel.collection = models;
//TODO: slow... esp. on start up
//if( multipanel.order !== 'update' ){
multipanel.sortCollection( multipanel.order, { silent: true });
//}
multipanel.sortCollection( multipanel.order, { silent: true });
multipanel.setUpCollectionListeners();
multipanel.trigger( 'new-collection', multipanel );
return multipanel;
@@ -408,11 +404,6 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
'change:deleted': multipanel.handleDeletedHistory,
'sort' : function(){ multipanel.renderColumns( 0 ); }
// debugging
//'all' : function(){
// console.info( 'collection:', arguments );
//}
});
},
@@ -430,11 +421,6 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
this.sortCollection();
////TODO: this actually means these render twice (1st from setCollection) - good enough for now
//if( oldCurrentColumn ){ oldCurrentColumn.render().delegateEvents(); }
//TODO:?? this occasionally causes race with hdaQueue
//newCurrentColumn.panel.render( 'fast' ).delegateEvents();
multipanel._recalcFirstColumnHeight();
return newCurrentColumn;
},
@@ -451,14 +437,14 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
// if it's the current column, create a new, empty history as the new current
if( column.model.id === this.currentHistoryId ){
//TODO: figuring out the order of async here is tricky - for now let the user handle the two step process
//TODO: figuring out the order of async here is tricky
// - for now let the user handle the two step process
//multipanel.collection.create().done( function(){
// if( !multipanel.collection.includeDeleted ){ multipanel.removeColumn( column, false ); }
//});
} else if( !multipanel.collection.includeDeleted ){
multipanel.removeColumn( column );
}
//TODO: prob. be done better
}
},
@@ -475,13 +461,12 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
//TODO: we can use a 2 arg version and return 1/0/-1
//this.collection.comparator = function( h1, h2 ){
this.collection.comparator = function( h ){
//TODO: this won't do reverse order well
//TODO: this won't do reverse order well
return [ h.id !== currentHistoryId, h.get( 'name' ).toLowerCase() ];
};
break;
case 'size':
this.collection.comparator = function( h ){
//console.debug( 'name sort', arguments )
return [ h.id !== currentHistoryId, h.get( 'size' ) ];
};
break;
@@ -501,6 +486,8 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
order = 'update';
}
this.order = order;
this.trigger( 'order:change', order, this );
this.$( '.current-order' ).text( order );
this.sortCollection();
return this;
},
@@ -533,6 +520,9 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
return column;
},
/** return array of Columns filtered by filters and sorted to match the collection
* @param: filters Function[] array of filter fns
*/
sortedFilteredColumns : function( filters ){
filters = filters || this.filters;
if( !filters || !filters.length ){
@@ -547,6 +537,7 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
});
},
/** return array of Columns sorted to match the collection */
sortedColumns : function(){
var multipanel = this;
var sorted = this.collection.map( function( history, index ){
@@ -555,9 +546,9 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
return sorted;
},
/** */
/** add a new column for history and render all columns if render is true */
addColumn : function add( history, render ){
//console.debug( 'adding column for:', history );
//this.debug( 'adding column for:', history );
render = render !== undefined? render: true;
var newColumn = this.createColumn( history );
this.columnMap[ history.id ] = newColumn;
@@ -567,9 +558,9 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
return newColumn;
},
/** */
/** add a new column for history and make it the current history/column */
addAsCurrentColumn : function add( history ){
//console.log( 'adding current column for:', history );
//this.log( 'adding current column for:', history );
var multipanel = this,
newColumn = this.addColumn( history, false );
this.setCurrentHistory( history );
@@ -579,7 +570,7 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
return newColumn;
},
/** */
/** remove the given column, it's listeners, and optionally render */
removeColumn : function remove( column, render ){
render = render !== undefined? render : true;
this.log( 'removeColumn', column );
@@ -657,79 +648,17 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
var multipanel = this;
multipanel.log( multipanel + '.render' );
multipanel.$el.html( multipanel.template( multipanel.options ) );
//console.debug( multipanel.$( '.loading-overlay' ).fadeIn( 0 ) );
multipanel.$el.html( multipanel.mainTemplate( multipanel ) );
multipanel.renderColumns( speed );
//console.debug( multipanel.$( '.loading-overlay' ).fadeOut( 'fast' ) );
// set the columns to full height allowed and set up behaviors for thie multipanel
multipanel.setUpBehaviors();
//TODO: wrong - has to wait for columns to render
//TODO: wrong - has to wait for columns to render
// - create a column listener that fires this when all columns are rendered
multipanel.trigger( 'rendered', multipanel );
return multipanel;
},
/** Template - overall structure relies on flex-boxes and is 3 components: header, middle, footer */
template : function template( options ){
options = options || {};
var html = [];
if( this.options.headerHeight ){
html = html.concat([
// a loading overlay
'<div class="loading-overlay flex-row"><div class="loading-overlay-message">loading...</div></div>',
'<div class="header flex-column-container">',
// page & history controls
'<div class="header-control header-control-left flex-column">',
'<button class="done btn btn-default">', _l( 'Done' ), '</button>',
'<button class="include-deleted btn btn-default"></button>',
'<div class="order btn-group">',
'<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">',
_l( 'Order histories by' ) + '... <span class="caret"></span>',
'</button>',
'<ul class="dropdown-menu" role="menu">',
'<li><a href="javascript:void(0);" class="order-update">',
_l( 'Time of last update' ),
'</a></li>',
'<li><a href="javascript:void(0);" class="order-name">',
_l( 'Name' ),
'</a></li>',
'<li><a href="javascript:void(0);" class="order-size">',
_l( 'Size' ),
'</a></li>',
'</ul>',
'</div>',
'<div id="search-histories" class="header-search"></div>',
'</div>',
// feedback
'<div class="header-control header-control-center flex-column">',
'<div class="header-info">',
'</div>',
'</div>',
// dataset controls
'<div class="header-control header-control-right flex-column">',
'<div id="search-datasets" class="header-search"></div>',
'<button id="toggle-deleted" class="btn btn-default">',
_l( 'Include deleted datasets' ),
'</button>',
'<button id="toggle-hidden" class="btn btn-default">',
_l( 'Include hidden datasets' ),
'</button>',
'</div>',
'</div>'
]);
}
html = html.concat([
// middle - where the columns go
'<div class="outer-middle flex-row flex-row-container">',
'<div class="middle flex-column-container flex-row"></div>',
'</div>',
// footer
'<div class="footer flex-column-container">','</div>'
]);
return $( html.join( '' ) );
},
/** Render the columns and panels */
renderColumns : function renderColumns( speed ){
speed = speed !== undefined? speed: this.fxSpeed;
@@ -737,61 +666,21 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
// render columns and track the total number rendered, firing an event when all are rendered
var multipanel = this,
sortedAndFiltered = multipanel.sortedFilteredColumns();
//console.log( '\t columnMapLength:', this.columnMapLength(), this.columnMap );
//this.log( '\t sortedAndFiltered:', sortedAndFiltered );
// set up width based on collection size
//console.debug( '(render) width before:', multipanel.$( '.middle' ).width() )
multipanel.$( '.middle' ).width( sortedAndFiltered.length
//TODO: magic number 16 === the amount that safely prevents stacking of columns when adding a new one
* ( this.options.columnWidth + this.options.columnGap ) + this.options.columnGap + 16 );
//console.debug( '(render) width now:', multipanel.$( '.middle' ).width() )
//console.debug( 'sortedAndFiltered:', sortedAndFiltered )
//multipanel.$( '.middle' ).empty();
// this.$( '.middle' ).html( sortedAndFiltered.map( function( column, i ){
// return column.$el.hide();
// }));
// sortedAndFiltered.forEach( function( column, i ){
////console.debug( 'rendering:', column, i )
// //multipanel.$( '.middle' ).append( column.$el.hide() );
// column.delegateEvents();
////TODO: current column in-view is never fired
// multipanel.renderColumn( column, speed ).$el.show();
// });
// var $els = sortedAndFiltered.map( function( column, i ){
////console.debug( 'rendering:', column, i )
// multipanel.renderColumn( column, speed );
// return column.$el;
// });
//// this breaks the event map
// //this.$( '.middle' ).html( $els );
//// this doesn't
// this.$( '.middle' ).append( $els );
var $middle = multipanel.$( '.middle' );
$middle.empty();
sortedAndFiltered.forEach( function( column, i ){
//console.debug( 'rendering:', column, i, column.panel )
column.$el.appendTo( $middle );
column.delegateEvents();
multipanel.renderColumn( column, speed );
//column.$el.hide().appendTo( $middle );
//multipanel.renderColumn( column, speed );
// //.panel.on( 'all', function(){
// // console.debug( 'column rendered:', arguments );
// //});
//// this won't work until we checkColumnsInView after the render
////column.$el.fadeIn( speed );
//column.$el.show();
});
//this.log( 'column rendering done' );
//TODO: event columns-rendered
//TODO: event columns-rendered
if( this.searchFor && sortedAndFiltered.length <= 1 ){
} else {
@@ -808,15 +697,11 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
renderColumn : function( column, speed ){
speed = speed !== undefined? speed: this.fxSpeed;
return column.render( speed );
//TODO: causes weirdness
//return _.delay( function(){
// return column.render( speed );
//}, 0 );
},
//TODO: combine the following two more sensibly
//TODO: could have HistoryContents.haveDetails return false
// if column.model.contents.length === 0 && !column.model.get( 'empty' ) then just check that
//TODO: combine the following two more sensibly
//TODO: could have HistoryContents.haveDetails return false
// if column.model.contents.length === 0 && !column.model.get( 'empty' ) then just check that
/** Get the *summary* contents of a column's history (and details on any expanded contents),
* queueing the ajax call and using a named queue to prevent the call being sent twice
*/
@@ -874,14 +759,17 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
events : {
// will move to the server root (gen. Analyze data)
'click .done.btn' : 'close',
//TODO:?? could just go back - but that's not always correct/desired behav.
//'click .done.btn' : function(){ window.history.back(); },
// creates a new empty history and makes it current
'click .create-new.btn' : 'create',
'click #include-deleted' : '_clickToggleDeletedHistories',
// these change the collection and column sort order
'click .order .order-update' : function( e ){ this.setOrder( 'update' ); },
'click .order .order-name' : function( e ){ this.setOrder( 'name' ); },
'click .order .order-size' : function( e ){ this.setOrder( 'size' ); }
'click .order .order-size' : function( e ){ this.setOrder( 'size' ); },
'click #toggle-deleted' : '_clickToggleDeletedDatasets',
'click #toggle-hidden' : '_clickToggleHiddenDatasets'
//'dragstart .list-item .title-bar' : function( e ){ console.debug( 'ok' ); }
},
@@ -897,37 +785,48 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
window.location = destination;
},
_clickToggleDeletedHistories : function( ev ){
return this.toggleDeletedHistories( $( ev.currentTarget ).is( ':checked' ) );
},
/** Include deleted histories in the collection */
includeDeletedHistories : function(){
//TODO: better through API/limit+offset
window.location += ( /\?/.test( window.location.toString() ) )?( '&' ):( '?' )
+ 'include_deleted_histories=True';
toggleDeletedHistories : function( show ){
if( show ){
window.location = Galaxy.options.root + 'history/view_multiple?include_deleted_histories=True';
} else {
window.location = Galaxy.options.root + 'history/view_multiple';
}
},
/** Show only non-deleted histories */
excludeDeletedHistories : function(){
//TODO: better through API/limit+offset
window.location = window.location.toString().replace( /[&\?]include_deleted_histories=True/g, '' );
_clickToggleDeletedDatasets : function( ev ){
return this.toggleDeletedDatasets( $( ev.currentTarget ).is( ':checked' ) );
},
toggleDeletedDatasets : function( show ){
show = show !== undefined? show : false;
var multipanel = this;
multipanel.sortedFilteredColumns().forEach( function( column, i ){
_.delay( function(){
column.panel.toggleShowDeleted( show, false );
}, i * 200 );
});
},
_clickToggleHiddenDatasets : function( ev ){
return this.toggleHiddenDatasets( $( ev.currentTarget ).is( ':checked' ) );
},
toggleHiddenDatasets : function( show ){
show = show !== undefined? show : false;
var multipanel = this;
multipanel.sortedFilteredColumns().forEach( function( column, i ){
_.delay( function(){
column.panel.toggleShowHidden( show, false );
}, i * 200 );
});
},
/** Set up any view plugins */
setUpBehaviors : function(){
var multipanel = this;
//TODO: currently doesn't need to be a mode button
// toggle button for include deleted
multipanel.$( '.include-deleted' ).modeButton({
initialMode : this.collection.includeDeleted? 'exclude' : 'include',
switchModesOnClick : false,
modes: [
{ mode: 'include', html: _l( 'Include deleted histories' ),
onclick: _.bind( multipanel.includeDeletedHistories, multipanel )
},
{ mode: 'exclude', html: _l( 'Exclude deleted histories' ),
onclick: _.bind( multipanel.excludeDeletedHistories, multipanel )
}
]
});
multipanel._moreOptionsPopover();
// input to search histories
multipanel.$( '#search-histories' ).searchInput({
@@ -942,7 +841,7 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
},
onclear : function( searchFor ){
multipanel.searchFor = null;
//TODO: remove specifically not just reset
//TODO: remove specifically not just reset
multipanel.filters = [];
multipanel.renderColumns( 0 );
}
@@ -986,53 +885,25 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
}
});
//TODO: each panel stores the hidden/deleted state - and that isn't reflected in the buttons
// toggle button for showing deleted history contents
multipanel.$( '#toggle-deleted' ).modeButton({
initialMode : 'include',
modes: [
{ mode: 'exclude', html: _l( 'Exclude deleted datasets' ) },
{ mode: 'include', html: _l( 'Include deleted datasets' ) }
]
}).click( function(){
var show = $( this ).modeButton( 'getMode' ).mode === 'exclude';
multipanel.sortedFilteredColumns().forEach( function( column, i ){
_.delay( function(){
column.panel.toggleShowDeleted( show, false );
}, i * 200 );
});
});
// toggle button for showing hidden history contents
multipanel.$( '#toggle-hidden' ).modeButton({
initialMode : 'include',
modes: [
{ mode: 'exclude', html: _l( 'Exclude hidden datasets' ) },
{ mode: 'include', html: _l( 'Include hidden datasets' ) }
]
}).click( function(){
var show = $( this ).modeButton( 'getMode' ).mode === 'exclude';
multipanel.sortedFilteredColumns().forEach( function( column, i ){
_.delay( function(){
column.panel.toggleShowHidden( show, false );
}, i * 200 );
});
});
// resize first (fixed position) column on page resize
$( window ).resize( function(){
multipanel._recalcFirstColumnHeight();
});
// when scrolling - check for histories now in view: they will fire 'in-view' and queueHdaLoading if necc.
//TODO:?? might be able to simplify and not use pub-sub
//TODO:?? might be able to simplify and not use pub-sub
var debouncedInView = _.debounce( _.bind( this.checkColumnsInView, this ), 100 );
this.$( '.middle' ).parent().scroll( debouncedInView );
},
///** Put the in-view columns then the other columns in a queue, rendering each one at a time */
//panelRenderQueue : function( columns, fn, args, renderEventName ){
//},
_moreOptionsPopover : function(){
return this.$( '.open-more-options.btn' ).popover({
container : '.header',
placement : 'bottom',
html : true,
content : $( this.optionsPopoverTemplate( this ) )
});
},
/** Adjust the height of the first, current column since flex-boxes won't work with fixed postiion elements */
_recalcFirstColumnHeight : function(){
@@ -1052,18 +923,17 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
/** returns the columns currently in the viewport */
columnsInView : function(){
//TODO: uses offset which is render intensive
//TODO: 2N - could use arg filter (sortedFilteredColumns( filter )) instead
//TODO: 2N - could use arg filter (sortedFilteredColumns( filter )) instead
var vp = this._viewport();
return this.sortedFilteredColumns().filter( function( column ){
return column.currentHistory || column.inView( vp.left, vp.right );
});
},
//TODO: sortByInView - return cols in view, then others
//TODO: sortByInView - return cols in view, then others
/** trigger in-view from columns in-view */
checkColumnsInView : function(){
//TODO: assbackward
//console.debug( 'checking columns in view', this.columnsInView() );
//TODO: assbackward - don't fire from the column, fire from here and listen from here
this.columnsInView().forEach( function( column ){
column.trigger( 'in-view', column );
});
@@ -1073,7 +943,7 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
currentColumnDropTargetOn : function(){
var currentColumn = this.columnMap[ this.currentHistoryId ];
if( !currentColumn ){ return; }
//TODO: fix this - shouldn't need monkeypatch
//TODO: fix this - shouldn't need monkeypatch
currentColumn.panel.dataDropped = function( data ){};
currentColumn.panel.dropTargetOn();
},
@@ -1090,7 +960,73 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
/** String rep */
toString : function(){
return 'MultiPanelColumns(' + ( this.columns? this.columns.length : 0 ) + ')';
}
},
// ------------------------------------------------------------------------ templates
mainTemplate : _.template([
'<div class="header flex-column-container">',
'<div class="control-column control-column-left flex-column">',
'<button class="create-new btn btn-default">', _l( 'Create new' ), '</button> ',
'<div id="search-histories" class="search-control"></div>',
'<div id="search-datasets" class="search-control"></div>',
'<button class="open-more-options btn btn-default">',
//TODO: tip not working
'<span class="fa fa-ellipsis-h" title="More options"></span>',
'</button>',
'</div>',
// feedback
'<div class="control-column control-column-center flex-column">',
'<div class="header-info">',
'</div>',
'</div>',
'<div class="control-column control-column-right flex-column">',
'<button class="done btn btn-default">', _l( 'Done' ), '</button>',
'</div>',
'</div>',
// middle - where the columns go
'<div class="outer-middle flex-row flex-row-container">',
'<div class="middle flex-column-container flex-row"></div>',
'</div>',
// footer
'<div class="footer flex-column-container">',
'</div>'
].join(''), { variable: 'view' }),
optionsPopoverTemplate : _.template([
'<div class="more-options">',
'<div class="checkbox"><label><input id="include-deleted" type="checkbox"',
'<%= view.collection.includeDeleted? " checked" : "" %>>',
_l( 'Include deleted histories' ),
'</label></div>',
'<div class="order btn-group">',
'<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">',
_l( 'Order histories by' ) + ' ',
'<span class="current-order"><%= view.order %></span> ',
'<span class="caret"></span>',
'</button>',
'<ul class="dropdown-menu" role="menu">',
'<li><a href="javascript:void(0);" class="order-update">',
_l( 'Time of last update' ),
'</a></li>',
'<li><a href="javascript:void(0);" class="order-name">',
_l( 'Name' ),
'</a></li>',
'<li><a href="javascript:void(0);" class="order-size">',
_l( 'Size' ),
'</a></li>',
'</ul>',
'</div>',
'<div class="checkbox"><label><input id="toggle-deleted" type="checkbox">',
_l( 'Include deleted datasets' ),
'</label></div>',
'<div class="checkbox"><label><input id="toggle-hidden" type="checkbox">',
_l( 'Include hidden datasets' ),
'</label></div>',
'</div>'
].join(''), { variable: 'view' }),
});
+146 -210
View File
@@ -6,7 +6,6 @@ define([
"ui/mode-button",
"ui/search-input"
], function( HISTORY_MODEL, HPANEL_EDIT, baseMVC, ajaxQueue ){
window.HISTORY_MODEL = HISTORY_MODEL;
//==============================================================================
/** */
function historyCopyDialog( history, options ){
@@ -265,7 +264,7 @@ var HistoryPanelColumn = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
return ( this.currentHistory )?
[
'<div class="pull-left">',
'<button class="create-new btn btn-default">', _l( 'Create new' ), '</button> ',
'<strong class="current-label">', _l( 'Current History' ), '</strong>',
'</div>'
].join( '' )
:[
@@ -328,6 +327,7 @@ var HistoryPanelColumn = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
//logger : console,
className : 'multi-history-columns',
// ------------------------------------------------------------------------ set up
/** Set up internals, history collection, and columns to display the history */
@@ -366,9 +366,8 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
/** model id to column map */
this.columnMap = {};
//TODO: why create here?
//TODO: why create here?
this.createColumns( options.columnOptions );
this.setUpListeners();
},
@@ -386,10 +385,7 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
var multipanel = this;
multipanel.stopListening( multipanel.collection );
multipanel.collection = models;
//TODO: slow... esp. on start up
//if( multipanel.order !== 'update' ){
multipanel.sortCollection( multipanel.order, { silent: true });
//}
multipanel.sortCollection( multipanel.order, { silent: true });
multipanel.setUpCollectionListeners();
multipanel.trigger( 'new-collection', multipanel );
return multipanel;
@@ -408,11 +404,6 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
'change:deleted': multipanel.handleDeletedHistory,
'sort' : function(){ multipanel.renderColumns( 0 ); }
// debugging
//'all' : function(){
// console.info( 'collection:', arguments );
//}
});
},
@@ -430,11 +421,6 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
this.sortCollection();
////TODO: this actually means these render twice (1st from setCollection) - good enough for now
//if( oldCurrentColumn ){ oldCurrentColumn.render().delegateEvents(); }
//TODO:?? this occasionally causes race with hdaQueue
//newCurrentColumn.panel.render( 'fast' ).delegateEvents();
multipanel._recalcFirstColumnHeight();
return newCurrentColumn;
},
@@ -451,14 +437,14 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
// if it's the current column, create a new, empty history as the new current
if( column.model.id === this.currentHistoryId ){
//TODO: figuring out the order of async here is tricky - for now let the user handle the two step process
//TODO: figuring out the order of async here is tricky
// - for now let the user handle the two step process
//multipanel.collection.create().done( function(){
// if( !multipanel.collection.includeDeleted ){ multipanel.removeColumn( column, false ); }
//});
} else if( !multipanel.collection.includeDeleted ){
multipanel.removeColumn( column );
}
//TODO: prob. be done better
}
},
@@ -475,13 +461,12 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
//TODO: we can use a 2 arg version and return 1/0/-1
//this.collection.comparator = function( h1, h2 ){
this.collection.comparator = function( h ){
//TODO: this won't do reverse order well
//TODO: this won't do reverse order well
return [ h.id !== currentHistoryId, h.get( 'name' ).toLowerCase() ];
};
break;
case 'size':
this.collection.comparator = function( h ){
//console.debug( 'name sort', arguments )
return [ h.id !== currentHistoryId, h.get( 'size' ) ];
};
break;
@@ -501,6 +486,8 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
order = 'update';
}
this.order = order;
this.trigger( 'order:change', order, this );
this.$( '.current-order' ).text( order );
this.sortCollection();
return this;
},
@@ -533,6 +520,9 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
return column;
},
/** return array of Columns filtered by filters and sorted to match the collection
* @param: filters Function[] array of filter fns
*/
sortedFilteredColumns : function( filters ){
filters = filters || this.filters;
if( !filters || !filters.length ){
@@ -547,6 +537,7 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
});
},
/** return array of Columns sorted to match the collection */
sortedColumns : function(){
var multipanel = this;
var sorted = this.collection.map( function( history, index ){
@@ -555,9 +546,9 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
return sorted;
},
/** */
/** add a new column for history and render all columns if render is true */
addColumn : function add( history, render ){
//console.debug( 'adding column for:', history );
//this.debug( 'adding column for:', history );
render = render !== undefined? render: true;
var newColumn = this.createColumn( history );
this.columnMap[ history.id ] = newColumn;
@@ -567,9 +558,9 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
return newColumn;
},
/** */
/** add a new column for history and make it the current history/column */
addAsCurrentColumn : function add( history ){
//console.log( 'adding current column for:', history );
//this.log( 'adding current column for:', history );
var multipanel = this,
newColumn = this.addColumn( history, false );
this.setCurrentHistory( history );
@@ -579,7 +570,7 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
return newColumn;
},
/** */
/** remove the given column, it's listeners, and optionally render */
removeColumn : function remove( column, render ){
render = render !== undefined? render : true;
this.log( 'removeColumn', column );
@@ -657,79 +648,17 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
var multipanel = this;
multipanel.log( multipanel + '.render' );
multipanel.$el.html( multipanel.template( multipanel.options ) );
//console.debug( multipanel.$( '.loading-overlay' ).fadeIn( 0 ) );
multipanel.$el.html( multipanel.mainTemplate( multipanel ) );
multipanel.renderColumns( speed );
//console.debug( multipanel.$( '.loading-overlay' ).fadeOut( 'fast' ) );
// set the columns to full height allowed and set up behaviors for thie multipanel
multipanel.setUpBehaviors();
//TODO: wrong - has to wait for columns to render
//TODO: wrong - has to wait for columns to render
// - create a column listener that fires this when all columns are rendered
multipanel.trigger( 'rendered', multipanel );
return multipanel;
},
/** Template - overall structure relies on flex-boxes and is 3 components: header, middle, footer */
template : function template( options ){
options = options || {};
var html = [];
if( this.options.headerHeight ){
html = html.concat([
// a loading overlay
'<div class="loading-overlay flex-row"><div class="loading-overlay-message">loading...</div></div>',
'<div class="header flex-column-container">',
// page & history controls
'<div class="header-control header-control-left flex-column">',
'<button class="done btn btn-default">', _l( 'Done' ), '</button>',
'<button class="include-deleted btn btn-default"></button>',
'<div class="order btn-group">',
'<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">',
_l( 'Order histories by' ) + '... <span class="caret"></span>',
'</button>',
'<ul class="dropdown-menu" role="menu">',
'<li><a href="javascript:void(0);" class="order-update">',
_l( 'Time of last update' ),
'</a></li>',
'<li><a href="javascript:void(0);" class="order-name">',
_l( 'Name' ),
'</a></li>',
'<li><a href="javascript:void(0);" class="order-size">',
_l( 'Size' ),
'</a></li>',
'</ul>',
'</div>',
'<div id="search-histories" class="header-search"></div>',
'</div>',
// feedback
'<div class="header-control header-control-center flex-column">',
'<div class="header-info">',
'</div>',
'</div>',
// dataset controls
'<div class="header-control header-control-right flex-column">',
'<div id="search-datasets" class="header-search"></div>',
'<button id="toggle-deleted" class="btn btn-default">',
_l( 'Include deleted datasets' ),
'</button>',
'<button id="toggle-hidden" class="btn btn-default">',
_l( 'Include hidden datasets' ),
'</button>',
'</div>',
'</div>'
]);
}
html = html.concat([
// middle - where the columns go
'<div class="outer-middle flex-row flex-row-container">',
'<div class="middle flex-column-container flex-row"></div>',
'</div>',
// footer
'<div class="footer flex-column-container">','</div>'
]);
return $( html.join( '' ) );
},
/** Render the columns and panels */
renderColumns : function renderColumns( speed ){
speed = speed !== undefined? speed: this.fxSpeed;
@@ -737,61 +666,21 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
// render columns and track the total number rendered, firing an event when all are rendered
var multipanel = this,
sortedAndFiltered = multipanel.sortedFilteredColumns();
//console.log( '\t columnMapLength:', this.columnMapLength(), this.columnMap );
//this.log( '\t sortedAndFiltered:', sortedAndFiltered );
// set up width based on collection size
//console.debug( '(render) width before:', multipanel.$( '.middle' ).width() )
multipanel.$( '.middle' ).width( sortedAndFiltered.length
//TODO: magic number 16 === the amount that safely prevents stacking of columns when adding a new one
* ( this.options.columnWidth + this.options.columnGap ) + this.options.columnGap + 16 );
//console.debug( '(render) width now:', multipanel.$( '.middle' ).width() )
//console.debug( 'sortedAndFiltered:', sortedAndFiltered )
//multipanel.$( '.middle' ).empty();
// this.$( '.middle' ).html( sortedAndFiltered.map( function( column, i ){
// return column.$el.hide();
// }));
// sortedAndFiltered.forEach( function( column, i ){
////console.debug( 'rendering:', column, i )
// //multipanel.$( '.middle' ).append( column.$el.hide() );
// column.delegateEvents();
////TODO: current column in-view is never fired
// multipanel.renderColumn( column, speed ).$el.show();
// });
// var $els = sortedAndFiltered.map( function( column, i ){
////console.debug( 'rendering:', column, i )
// multipanel.renderColumn( column, speed );
// return column.$el;
// });
//// this breaks the event map
// //this.$( '.middle' ).html( $els );
//// this doesn't
// this.$( '.middle' ).append( $els );
var $middle = multipanel.$( '.middle' );
$middle.empty();
sortedAndFiltered.forEach( function( column, i ){
//console.debug( 'rendering:', column, i, column.panel )
column.$el.appendTo( $middle );
column.delegateEvents();
multipanel.renderColumn( column, speed );
//column.$el.hide().appendTo( $middle );
//multipanel.renderColumn( column, speed );
// //.panel.on( 'all', function(){
// // console.debug( 'column rendered:', arguments );
// //});
//// this won't work until we checkColumnsInView after the render
////column.$el.fadeIn( speed );
//column.$el.show();
});
//this.log( 'column rendering done' );
//TODO: event columns-rendered
//TODO: event columns-rendered
if( this.searchFor && sortedAndFiltered.length <= 1 ){
} else {
@@ -808,15 +697,11 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
renderColumn : function( column, speed ){
speed = speed !== undefined? speed: this.fxSpeed;
return column.render( speed );
//TODO: causes weirdness
//return _.delay( function(){
// return column.render( speed );
//}, 0 );
},
//TODO: combine the following two more sensibly
//TODO: could have HistoryContents.haveDetails return false
// if column.model.contents.length === 0 && !column.model.get( 'empty' ) then just check that
//TODO: combine the following two more sensibly
//TODO: could have HistoryContents.haveDetails return false
// if column.model.contents.length === 0 && !column.model.get( 'empty' ) then just check that
/** Get the *summary* contents of a column's history (and details on any expanded contents),
* queueing the ajax call and using a named queue to prevent the call being sent twice
*/
@@ -874,14 +759,17 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
events : {
// will move to the server root (gen. Analyze data)
'click .done.btn' : 'close',
//TODO:?? could just go back - but that's not always correct/desired behav.
//'click .done.btn' : function(){ window.history.back(); },
// creates a new empty history and makes it current
'click .create-new.btn' : 'create',
'click #include-deleted' : '_clickToggleDeletedHistories',
// these change the collection and column sort order
'click .order .order-update' : function( e ){ this.setOrder( 'update' ); },
'click .order .order-name' : function( e ){ this.setOrder( 'name' ); },
'click .order .order-size' : function( e ){ this.setOrder( 'size' ); }
'click .order .order-size' : function( e ){ this.setOrder( 'size' ); },
'click #toggle-deleted' : '_clickToggleDeletedDatasets',
'click #toggle-hidden' : '_clickToggleHiddenDatasets'
//'dragstart .list-item .title-bar' : function( e ){ console.debug( 'ok' ); }
},
@@ -897,37 +785,48 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
window.location = destination;
},
_clickToggleDeletedHistories : function( ev ){
return this.toggleDeletedHistories( $( ev.currentTarget ).is( ':checked' ) );
},
/** Include deleted histories in the collection */
includeDeletedHistories : function(){
//TODO: better through API/limit+offset
window.location += ( /\?/.test( window.location.toString() ) )?( '&' ):( '?' )
+ 'include_deleted_histories=True';
toggleDeletedHistories : function( show ){
if( show ){
window.location = Galaxy.options.root + 'history/view_multiple?include_deleted_histories=True';
} else {
window.location = Galaxy.options.root + 'history/view_multiple';
}
},
/** Show only non-deleted histories */
excludeDeletedHistories : function(){
//TODO: better through API/limit+offset
window.location = window.location.toString().replace( /[&\?]include_deleted_histories=True/g, '' );
_clickToggleDeletedDatasets : function( ev ){
return this.toggleDeletedDatasets( $( ev.currentTarget ).is( ':checked' ) );
},
toggleDeletedDatasets : function( show ){
show = show !== undefined? show : false;
var multipanel = this;
multipanel.sortedFilteredColumns().forEach( function( column, i ){
_.delay( function(){
column.panel.toggleShowDeleted( show, false );
}, i * 200 );
});
},
_clickToggleHiddenDatasets : function( ev ){
return this.toggleHiddenDatasets( $( ev.currentTarget ).is( ':checked' ) );
},
toggleHiddenDatasets : function( show ){
show = show !== undefined? show : false;
var multipanel = this;
multipanel.sortedFilteredColumns().forEach( function( column, i ){
_.delay( function(){
column.panel.toggleShowHidden( show, false );
}, i * 200 );
});
},
/** Set up any view plugins */
setUpBehaviors : function(){
var multipanel = this;
//TODO: currently doesn't need to be a mode button
// toggle button for include deleted
multipanel.$( '.include-deleted' ).modeButton({
initialMode : this.collection.includeDeleted? 'exclude' : 'include',
switchModesOnClick : false,
modes: [
{ mode: 'include', html: _l( 'Include deleted histories' ),
onclick: _.bind( multipanel.includeDeletedHistories, multipanel )
},
{ mode: 'exclude', html: _l( 'Exclude deleted histories' ),
onclick: _.bind( multipanel.excludeDeletedHistories, multipanel )
}
]
});
multipanel._moreOptionsPopover();
// input to search histories
multipanel.$( '#search-histories' ).searchInput({
@@ -942,7 +841,7 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
},
onclear : function( searchFor ){
multipanel.searchFor = null;
//TODO: remove specifically not just reset
//TODO: remove specifically not just reset
multipanel.filters = [];
multipanel.renderColumns( 0 );
}
@@ -986,53 +885,25 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
}
});
//TODO: each panel stores the hidden/deleted state - and that isn't reflected in the buttons
// toggle button for showing deleted history contents
multipanel.$( '#toggle-deleted' ).modeButton({
initialMode : 'include',
modes: [
{ mode: 'exclude', html: _l( 'Exclude deleted datasets' ) },
{ mode: 'include', html: _l( 'Include deleted datasets' ) }
]
}).click( function(){
var show = $( this ).modeButton( 'getMode' ).mode === 'exclude';
multipanel.sortedFilteredColumns().forEach( function( column, i ){
_.delay( function(){
column.panel.toggleShowDeleted( show, false );
}, i * 200 );
});
});
// toggle button for showing hidden history contents
multipanel.$( '#toggle-hidden' ).modeButton({
initialMode : 'include',
modes: [
{ mode: 'exclude', html: _l( 'Exclude hidden datasets' ) },
{ mode: 'include', html: _l( 'Include hidden datasets' ) }
]
}).click( function(){
var show = $( this ).modeButton( 'getMode' ).mode === 'exclude';
multipanel.sortedFilteredColumns().forEach( function( column, i ){
_.delay( function(){
column.panel.toggleShowHidden( show, false );
}, i * 200 );
});
});
// resize first (fixed position) column on page resize
$( window ).resize( function(){
multipanel._recalcFirstColumnHeight();
});
// when scrolling - check for histories now in view: they will fire 'in-view' and queueHdaLoading if necc.
//TODO:?? might be able to simplify and not use pub-sub
//TODO:?? might be able to simplify and not use pub-sub
var debouncedInView = _.debounce( _.bind( this.checkColumnsInView, this ), 100 );
this.$( '.middle' ).parent().scroll( debouncedInView );
},
///** Put the in-view columns then the other columns in a queue, rendering each one at a time */
//panelRenderQueue : function( columns, fn, args, renderEventName ){
//},
_moreOptionsPopover : function(){
return this.$( '.open-more-options.btn' ).popover({
container : '.header',
placement : 'bottom',
html : true,
content : $( this.optionsPopoverTemplate( this ) )
});
},
/** Adjust the height of the first, current column since flex-boxes won't work with fixed postiion elements */
_recalcFirstColumnHeight : function(){
@@ -1052,18 +923,17 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
/** returns the columns currently in the viewport */
columnsInView : function(){
//TODO: uses offset which is render intensive
//TODO: 2N - could use arg filter (sortedFilteredColumns( filter )) instead
//TODO: 2N - could use arg filter (sortedFilteredColumns( filter )) instead
var vp = this._viewport();
return this.sortedFilteredColumns().filter( function( column ){
return column.currentHistory || column.inView( vp.left, vp.right );
});
},
//TODO: sortByInView - return cols in view, then others
//TODO: sortByInView - return cols in view, then others
/** trigger in-view from columns in-view */
checkColumnsInView : function(){
//TODO: assbackward
//console.debug( 'checking columns in view', this.columnsInView() );
//TODO: assbackward - don't fire from the column, fire from here and listen from here
this.columnsInView().forEach( function( column ){
column.trigger( 'in-view', column );
});
@@ -1073,7 +943,7 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
currentColumnDropTargetOn : function(){
var currentColumn = this.columnMap[ this.currentHistoryId ];
if( !currentColumn ){ return; }
//TODO: fix this - shouldn't need monkeypatch
//TODO: fix this - shouldn't need monkeypatch
currentColumn.panel.dataDropped = function( data ){};
currentColumn.panel.dropTargetOn();
},
@@ -1090,7 +960,73 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
/** String rep */
toString : function(){
return 'MultiPanelColumns(' + ( this.columns? this.columns.length : 0 ) + ')';
}
},
// ------------------------------------------------------------------------ templates
mainTemplate : _.template([
'<div class="header flex-column-container">',
'<div class="control-column control-column-left flex-column">',
'<button class="create-new btn btn-default">', _l( 'Create new' ), '</button> ',
'<div id="search-histories" class="search-control"></div>',
'<div id="search-datasets" class="search-control"></div>',
'<button class="open-more-options btn btn-default">',
//TODO: tip not working
'<span class="fa fa-ellipsis-h" title="More options"></span>',
'</button>',
'</div>',
// feedback
'<div class="control-column control-column-center flex-column">',
'<div class="header-info">',
'</div>',
'</div>',
'<div class="control-column control-column-right flex-column">',
'<button class="done btn btn-default">', _l( 'Done' ), '</button>',
'</div>',
'</div>',
// middle - where the columns go
'<div class="outer-middle flex-row flex-row-container">',
'<div class="middle flex-column-container flex-row"></div>',
'</div>',
// footer
'<div class="footer flex-column-container">',
'</div>'
].join(''), { variable: 'view' }),
optionsPopoverTemplate : _.template([
'<div class="more-options">',
'<div class="checkbox"><label><input id="include-deleted" type="checkbox"',
'<%= view.collection.includeDeleted? " checked" : "" %>>',
_l( 'Include deleted histories' ),
'</label></div>',
'<div class="order btn-group">',
'<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">',
_l( 'Order histories by' ) + ' ',
'<span class="current-order"><%= view.order %></span> ',
'<span class="caret"></span>',
'</button>',
'<ul class="dropdown-menu" role="menu">',
'<li><a href="javascript:void(0);" class="order-update">',
_l( 'Time of last update' ),
'</a></li>',
'<li><a href="javascript:void(0);" class="order-name">',
_l( 'Name' ),
'</a></li>',
'<li><a href="javascript:void(0);" class="order-size">',
_l( 'Size' ),
'</a></li>',
'</ul>',
'</div>',
'<div class="checkbox"><label><input id="toggle-deleted" type="checkbox">',
_l( 'Include deleted datasets' ),
'</label></div>',
'<div class="checkbox"><label><input id="toggle-hidden" type="checkbox">',
_l( 'Include hidden datasets' ),
'</label></div>',
'</div>'
].join(''), { variable: 'view' }),
});
File diff suppressed because one or more lines are too long
@@ -75,29 +75,36 @@
.flex-column {
}
/* ---------------------- header & footer */
.header, .footer {
width: 100%;
}
/* ---------------------- header */
.header {
background-color: lightgrey;
min-height: ${header_height}px;
max-height: ${header_height}px;
}
.header-control {
.control-column {
margin-top: 4px;
}
.header-control-right,
.header-control-left {
.control-column-right,
.control-column-left {
margin-right: 8px;
margin-left: 8px;
/*background-color: green;*/
}
.header-control-left > * {
.control-column-left > * {
margin: 0px 4px 4px 0px;
}
.more-options input[type=checkbox] {
margin-top: 3px;
}
.header-control-center {
.control-column-center {
text-align: center;
max-height: 22px;
-webkit-flex: 0 1 auto;
@@ -117,26 +124,35 @@
}
.header-control-right {
.control-column-right {
text-align: right;
}
.header-control-right > * {
.control-column-right > * {
margin: 0px 0px 4px 4px;
}
.header-search {
.search-control {
display: inline-block;
width: 40%;
}
.header-search .search-clear,
.header-search .search-loading {
.search-control .search-clear,
.search-control .search-loading {
margin-top: -22px;
}
.footer input.search-query,
.header input.search-query {
font-size: 90%;
height: 21px;
padding: 2px;
line-height: normal;
padding: 2px 2px 1px 2px;
}
.open-more-options {
padding: 0 4px 0 4px;
font-size: 150%;
}
/* ---------------------- middle */
.outer-middle {
overflow: auto;
}
@@ -169,13 +185,18 @@
align-self: auto;
}
.header .btn,
.footer .btn,
.panel-controls .btn {
height: ${controls_height}px;
line-height: ${controls_height - 2}px;
height: 20px;
/*line-height: ${controls_height - 2}px;*/
line-height: normal;
font-size: 90%;
padding-top: 0px;
padding-bottom: 0px;
}
.header .btn {
height: 21px;
}
.panel-controls .pull-left .btn {
margin-right: 4px;
}
@@ -183,13 +204,14 @@
margin-left: 4px;
}
/* ---------------------- footer */
.footer {
min-height: ${footer_height}px;
max-height: ${footer_height}px;
background-color: grey;
background-color: lightgrey;
}
/* ---- columns */
/* ---------------------- columns */
.history-panel {
width: 100%;
margin-top: 4px;
@@ -230,6 +252,12 @@
vertical-align: middle;
}
.current-label {
display: inline-block;
color: grey;
padding-left: 2px;
margin-top: 2px;
}
</style>
</%def>