1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/598a4ec015f6/ Changeset: 598a4ec015f6 User: carlfeberhard Date: 2014-07-23 18:59:04 Summary: History/Collection/HDA MVC: refactoring; Base MVC: create mixin shortcut to extend, use with HistoryContentMixin; Pages: allow display of collections; HDAs: fix annotation default overwriting existing when only partial model is saved; Style: begin separation of collections styles and prep for DOM class renaming; pack scripts Affected #: 50 files diff -r 74d7ff952ec902e68cc99745fdcd3c14e86208b2 -r 598a4ec015f66237d41ed2176c673fd52b744f15 lib/galaxy/webapps/galaxy/controllers/history.py --- a/lib/galaxy/webapps/galaxy/controllers/history.py +++ b/lib/galaxy/webapps/galaxy/controllers/history.py @@ -195,6 +195,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesItemRatings, UsesHistoryMixin, UsesHistoryDatasetAssociationMixin, ExportsHistoryMixin, ImportsHistoryMixin ): + def __init__( self, app ): super( HistoryController, self ).__init__( app ) self.mgrs = util.bunch.Bunch( diff -r 74d7ff952ec902e68cc99745fdcd3c14e86208b2 -r 598a4ec015f66237d41ed2176c673fd52b744f15 lib/galaxy/webapps/galaxy/controllers/page.py --- a/lib/galaxy/webapps/galaxy/controllers/page.py +++ b/lib/galaxy/webapps/galaxy/controllers/page.py @@ -1,5 +1,6 @@ from sqlalchemy import desc, and_ from galaxy import model, web +from galaxy import managers from galaxy.web import error, url_for from galaxy.model.item_attrs import UsesItemRatings from galaxy.web.base.controller import BaseUIController, SharableMixin, UsesHistoryMixin, UsesStoredWorkflowMixin, UsesVisualizationMixin @@ -285,6 +286,12 @@ _page_selection_grid = PageSelectionGrid() _visualization_selection_grid = VisualizationSelectionGrid() + def __init__( self, app ): + super( PageController, self ).__init__( app ) + self.mgrs = util.bunch.Bunch( + histories=managers.histories.HistoryManager() + ) + @web.expose @web.require_login() def list( self, trans, *args, **kwargs ): @@ -718,6 +725,7 @@ """ Returns html suitable for embedding in another page. """ + #TODO: should be moved to history controller and/or called via ajax from the template history = self.get_history( trans, id, False, True ) if not history: return None @@ -729,15 +737,22 @@ hda_dicts = [] datasets = self.get_history_datasets( trans, history ) - for hda in datasets: - hda_dict = self.get_hda_dict( trans, hda ) - hda_dict[ 'annotation' ] = self.get_item_annotation_str( trans.sa_session, history.user, hda ) - hda_dicts.append( hda_dict ) - history_dict = self.get_history_dict( trans, history, hda_dictionaries=hda_dicts ) - history_dict[ 'annotation' ] = history.annotation + #for hda in datasets: + # hda_dict = self.get_hda_dict( trans, hda ) + # hda_dict[ 'annotation' ] = self.get_item_annotation_str( trans.sa_session, history.user, hda ) + # hda_dicts.append( hda_dict ) + #history_dict = self.get_history_dict( trans, history, hda_dictionaries=hda_dicts ) + #history_dict[ 'annotation' ] = history.annotation + + # include all datasets: hidden, deleted, and purged + #TODO!: doubled query (hda_dictionaries + datasets) + history_data = self.mgrs.histories._get_history_data( trans, history ) + history_dictionary = history_data[ 'history' ] + hda_dictionaries = history_data[ 'contents' ] + history_dictionary[ 'annotation' ] = history.annotation filled = trans.fill_template( "history/embed.mako", item=history, item_data=datasets, - user_is_owner=user_is_owner, history_dict=history_dict, hda_dicts=hda_dicts ) + user_is_owner=user_is_owner, history_dict=history_dictionary, hda_dicts=hda_dictionaries ) return filled def _get_embed_html( self, trans, item_class, item_id ): diff -r 74d7ff952ec902e68cc99745fdcd3c14e86208b2 -r 598a4ec015f66237d41ed2176c673fd52b744f15 static/scripts/mvc/base-mvc.js --- a/static/scripts/mvc/base-mvc.js +++ b/static/scripts/mvc/base-mvc.js @@ -14,7 +14,7 @@ * @example * // Add to your models/views at the definition using chaining: * var MyModel = Backbone.Model.extend( LoggableMixin ).extend({ // ... }); - * + * * // or - more explicitly AFTER the definition: * var MyModel = Backbone.Model.extend({ * logger : console @@ -208,8 +208,23 @@ }; //============================================================================== -return { - LoggableMixin : LoggableMixin, - SessionStorageModel : SessionStorageModel, - HiddenUntilActivatedViewMixin : HiddenUntilActivatedViewMixin -};}); +function mixin( mixinHash1, /* mixinHash2, etc: ... variadic */ propsHash ){ + // usage: var NewModel = Something.extend( mixin( MyMixinA, MyMixinB, { ... }) ); + //NOTE: this does not combine any hashes (like events, etc.) and you're expected to handle that + + // simple reversal of param order on _.defaults() - to show mixins in top of definition + var args = Array.prototype.slice.call( arguments, 0 ), + lastArg = args.pop(); + args.unshift( lastArg ); + return _.defaults.apply( _, args ); +} + + +//============================================================================== + return { + LoggableMixin : LoggableMixin, + SessionStorageModel : SessionStorageModel, + HiddenUntilActivatedViewMixin : HiddenUntilActivatedViewMixin, + mixin : mixin + }; +}); diff -r 74d7ff952ec902e68cc99745fdcd3c14e86208b2 -r 598a4ec015f66237d41ed2176c673fd52b744f15 static/scripts/mvc/collection/collection-model.js --- /dev/null +++ b/static/scripts/mvc/collection/collection-model.js @@ -0,0 +1,250 @@ +define([ + "mvc/dataset/hda-model", + "mvc/base-mvc", + "utils/localization" +], function( HDA_MODEL, BASE_MVC, _l ){ +//============================================================================== +/** @class Backbone model for Dataset collection elements. + * DC Elements contain a sub-model named 'object'. This class moves that + * 'object' from the JSON in the attributes list to a full, instantiated + * sub-model found in this.object. This is done on intialization and + * everytime the 'change:object' event is fired. + * + * @borrows LoggableMixin#logger as #logger + * @borrows LoggableMixin#log as #log + * @constructs + */ +var DatasetCollectionElement = Backbone.Model.extend( BASE_MVC.LoggableMixin ).extend( +/** @lends DatasetCollectionElement.prototype */{ + //TODO:?? this model may be unneccessary - it reflects the api structure, but... + // if we munge the element with the element.object at parse, we can flatten the entire hierarchy + + /** logger used to record this.log messages, commonly set to console */ + // comment this out to suppress log output + //logger : console, + + defaults : { + id : null, + model_class : 'DatasetCollectionElement', + element_identifier : null, + element_index : null, + element_type : null + }, + + /** Set up. + * @see Backbone.Collection#initialize + */ + initialize : function( model, options ){ + this.info( this + '.initialize:', model, options ); + options = options || {}; + //this._setUpListeners(); + + this.object = this._createObjectModel(); + this.on( 'change:object', function(){ + //console.log( 'change:object' ); +//TODO: prob. better to update the sub-model instead of re-creating it + this.object = this._createObjectModel(); + }); + }, + + _createObjectModel : function(){ + //console.log( '_createObjectModel', this.get( 'object' ), this.object ); + //TODO: same patterns as HDCA _createElementsModel - refactor to BASE_MVC.hasSubModel? + if( _.isUndefined( this.object ) ){ this.object = null; } + if( !this.get( 'object' ) ){ return this.object; } + + var object = this.get( 'object' ); + this.unset( 'object', { silent: true }); + + this.debug( 'DCE, element_type:', this.get( 'element_type' ) ); + switch( this.get( 'element_type' ) ){ + case 'dataset_collection': + this.object = new DatasetCollection( object ); + break; + case 'hda': + this.object = new HDA_MODEL.HistoryDatasetAssociation( object ); + break; + default: + throw new TypeError( 'Unknown element_type: ' + this.get( 'element_type' ) ); + } + return this.object; + }, + + /** String representation. */ + toString : function(){ + var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) ); + return ([ 'DatasetCollectionElement(', objStr, ')' ].join( '' )); + } +}); + + +//============================================================================== +/** @class Backbone collection for DCEs. + * NOTE: used *only* in second level of list:paired collections (a + * collection that contains collections) + * + * @borrows LoggableMixin#logger as #logger + * @borrows LoggableMixin#log as #log + * @constructs + */ +var DatasetCollectionElementCollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend( +/** @lends DatasetCollectionElementCollection.prototype */{ + model: DatasetCollectionElement, + + // comment this out to suppress log output + /** logger used to record this.log messages, commonly set to console */ + //logger : console, + + /** Set up. + * @see Backbone.Collection#initialize + */ + initialize : function( models, options ){ + options = options || {}; + this.info( this + '.initialize:', models, options ); + //this._setUpListeners(); + }, + + /** String representation. */ + toString : function(){ + return ([ 'DatasetCollectionElementCollection(', this.length, ')' ].join( '' )); + } +}); + + +//============================================================================== +/** @class Backbone model for Dataset Collections. + * DCs contain a bbone collection named 'elements' using the class found in + * this.collectionClass (gen. DatasetCollectionElementCollection). DCs move + * that 'object' from the JSON in the attributes list to a full, instantiated + * collection found in this.elements. This is done on intialization and + * everytime the 'change:elements' event is fired. + * + * @borrows LoggableMixin#logger as #logger + * @borrows LoggableMixin#log as #log + * @constructs + */ +var DatasetCollection = Backbone.Model.extend( BASE_MVC.LoggableMixin ).extend( +/** @lends ListDatasetCollection.prototype */{ + + //logger : console, + + /** default attributes for a model */ + defaults : { + collection_type : 'list' + }, + + collectionClass : DatasetCollectionElementCollection, + + /** */ + initialize : function( model, options ){ + this.info( 'DatasetCollection.initialize:', model, options ); + //historyContent.HistoryContent.prototype.initialize.call( this, attrs, options ); + this.elements = this._createElementsModel(); +//TODO:?? no way to use parse here? + this.on( 'change:elements', function(){ + this.log( 'change:elements' ); +//TODO: prob. better to update the collection instead of re-creating it + this.elements = this._createElementsModel(); + }); + }, + + /** move elements model attribute to full collection */ + _createElementsModel : function(){ + this.log( '_createElementsModel', this.get( 'elements' ), this.elements ); +//TODO: same patterns as DatasetCollectionElement _createObjectModel - refactor to BASE_MVC.hasSubModel? + var elements = this.get( 'elements' ) || []; + this.info( 'elements:', elements ); + this.unset( 'elements', { silent: true }); + this.elements = new this.collectionClass( elements ); + return this.elements; + }, + + hasDetails : function(){ +//TODO: this is incorrect for (accidentally) empty collections + return this.elements.length !== 0; + }, + + // ........................................................................ misc + /** String representation */ + toString : function(){ + var idAndName = [ this.get( 'id' ), this.get( 'name' ) || this.get( 'element_identifier' ) ]; + return 'DatasetCollection(' + ( idAndName.join(',') ) + ')'; + } +}); + + +//============================================================================== +/** @class Backbone collection for a collection of collection collections collecting correctly. */ +var DatasetCollectionCollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend({ + + model: DatasetCollection, + + ///** logger used to record this.log messages, commonly set to console */ + //// comment this out to suppress log output + //logger : console, + + /** Set up. + * @see Backbone.Collection#initialize + */ + initialize : function( models, options ){ + options = options || {}; + this.info( 'DatasetCollectionCollection.initialize:', models, options ); + //this._setUpListeners(); + }, + + /** String representation. */ + toString : function(){ + return ([ 'DatasetCollectionCollection(', this.get( 'name' ), ')' ].join( '' )); + } +}); + + +//NOTE: the following prototypes may not be necessary - but I wanted to specifiy +// them (for now) and allow for the possibility of unique functionality +//============================================================================== +var ListDatasetCollection = DatasetCollection.extend( +/** @lends ListDatasetCollection.prototype */{ + + /** String representation. */ + toString : function(){ + return ([ 'ListDatasetCollection(', this.get( 'name' ), ')' ].join( '' )); + } +}); + + +//============================================================================== +var PairDatasetCollection = DatasetCollection.extend( +/** @lends ListDatasetCollection.prototype */{ + + /** String representation. */ + toString : function(){ + return ([ 'PairDatasetCollection(', this.get( 'name' ), ')' ].join( '' )); + } +}); + + +//============================================================================== +var ListPairedDatasetCollection = DatasetCollection.extend( +/** @lends ListDatasetCollection.prototype */{ + + // list:paired is the only collection that itself contains collections + //collectionClass : DatasetCollectionCollection, + + /** String representation. */ + toString : function(){ + return ([ 'ListPairedDatasetCollection(', this.get( 'name' ), ')' ].join( '' )); + } +}); + + +//============================================================================== + return { + DatasetCollectionElement : DatasetCollectionElement, + DatasetCollectionElementCollection : DatasetCollectionElementCollection, + DatasetCollection : DatasetCollection, + DatasetCollectionCollection : DatasetCollectionCollection, + ListDatasetCollection : ListDatasetCollection, + PairDatasetCollection : PairDatasetCollection, + ListPairedDatasetCollection : ListPairedDatasetCollection + }; +}); diff -r 74d7ff952ec902e68cc99745fdcd3c14e86208b2 -r 598a4ec015f66237d41ed2176c673fd52b744f15 static/scripts/mvc/collection/collection-panel.js --- /dev/null +++ b/static/scripts/mvc/collection/collection-panel.js @@ -0,0 +1,326 @@ +define([ + "mvc/collection/dataset-collection-base", + "mvc/dataset/hda-base", + "mvc/base-mvc", + "utils/localization" +], function( DC_BASE, HDA_BASE, BASE_MVC, _l ){ +/* ============================================================================= +TODO: + +============================================================================= */ +/** @class non-editable, read-only View/Controller for a dataset collection. + * @name CollectionPanel + * + * @augments Backbone.View + * @borrows LoggableMixin#logger as #logger + * @borrows LoggableMixin#log as #log + * @constructs + */ +var CollectionPanel = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend( +/** @lends CollectionPanel.prototype */{ + + /** logger used to record this.log messages, commonly set to console */ + // comment this out to suppress log output + //logger : console, + + tagName : 'div', + className : 'history-panel', + + /** (in ms) that jquery effects will use */ + fxSpeed : 'fast', + + // ......................................................................... SET UP + /** Set up the view, set up storage, bind listeners to HistoryContents events + * @param {Object} attributes optional settings for the panel + */ + initialize : function( attributes ){ + attributes = attributes || {}; + // set the logger if requested + if( attributes.logger ){ + this.logger = attributes.logger; + } + this.log( this + '.initialize:', attributes ); + + this.hasUser = attributes.hasUser; + this.HDAViewClass = attributes.HDAViewClass || HDA_BASE.HDABaseView; + }, + + /** create any event listeners for the panel + * @fires: rendered:initial on the first render + * @fires: empty-history when switching to a history with no HDAs or creating a new history + */ + _setUpListeners : function(){ + // debugging + //if( this.logger ){ + this.on( 'all', function( event ){ + this.log( this + '', arguments ); + }, this ); + //} + return this; + }, + + // ------------------------------------------------------------------------ history/hda event listening + /** listening for history and HDA events */ + _setUpModelEventHandlers : function(){ + return this; + }, + + // ------------------------------------------------------------------------ panel rendering + /** Render urls, historyPanel body, and hdas (if any are shown) + * @fires: rendered when the panel is attached and fully visible + * @see Backbone.View#render + */ + render : function( speed, callback ){ + this.log( 'render:', speed, callback ); + // send a speed of 0 to have no fade in/out performed + speed = ( speed === undefined )?( this.fxSpeed ):( speed ); + //console.debug( this + '.render, fxSpeed:', speed ); + var panel = this, + $newRender; + + // handle the possibility of no model (can occur if fetching the model returns an error) + if( !this.model ){ + return this; + } + $newRender = this.renderModel(); + + // fade out existing, swap with the new, fade in, set up behaviours + $( panel ).queue( 'fx', [ + function( next ){ + if( speed && panel.$el.is( ':visible' ) ){ + panel.$el.fadeOut( speed, next ); + } else { + next(); + } + }, + function( next ){ + // swap over from temp div newRender + panel.$el.empty(); + if( $newRender ){ + panel.$el.append( $newRender.children() ); + } + next(); + }, + function( next ){ + if( speed && !panel.$el.is( ':visible' ) ){ + panel.$el.fadeIn( speed, next ); + } else { + next(); + } + }, + function( next ){ + //TODO: ideally, these would be set up before the fade in (can't because of async save text) + if( callback ){ callback.call( this ); } + panel.trigger( 'rendered', this ); + next(); + } + ]); + return this; + }, + + /** render with history data + * @returns {jQuery} dom fragment as temporary container to be swapped out later + */ + renderModel : function( ){ + // tmp div for final swap in render + var $newRender = $( '<div/>' ).append( CollectionPanel.templates.panel( this.model.toJSON() ) ); + this._setUpBehaviours( $newRender ); + this.renderContents( $newRender ); + return $newRender; + }, + + /** Set up HistoryPanel js/widget behaviours */ + _setUpBehaviours : function( $where ){ + //TODO: these should be either sub-MVs, or handled by events + $where = $where || this.$el; + $where.find( '[title]' ).tooltip({ placement: 'bottom' }); + return this; + }, + + // ------------------------------------------------------------------------ sub-$element shortcuts + /** the scroll container for this panel - can be $el, $el.parent(), or grandparent depending on context */ + $container : function(){ + return ( this.findContainerFn )?( this.findContainerFn.call( this ) ):( this.$el.parent() ); + }, + /** where hdaViews are attached */ + $datasetsList : function( $where ){ + return ( $where || this.$el ).find( '.datasets-list' ); + }, + + // ------------------------------------------------------------------------ sub-views + /** Set up/render a view for each HDA to be shown, init with model and listeners. + * HDA views are cached to the map this.hdaViews (using the model.id as key). + * @param {jQuery} $whereTo what dom element to prepend the HDA views to + * @returns the number of visible hda views + */ + renderContents : function( $whereTo ){ + //console.debug( 'renderContents, elements:', this.model.elements ); + $whereTo = $whereTo || this.$el; + + var panel = this, + contentViews = {}, + visibleContents = this.model.elements || []; + //this.log( 'renderContents, visibleContents:', visibleContents, $whereTo ); + + this.$datasetsList( $whereTo ).empty(); + if( visibleContents && visibleContents.length ){ + visibleContents.each( function( content ){ + var contentId = content.id, + contentView = panel._createContentView( content ); + contentViews[ contentId ] = contentView; + panel.attachContentView( contentView.render(), $whereTo ); + }); + } + this.contentViews = contentViews; + return this.contentViews; + }, + + /** + * @param {HistoryDatasetAssociation} content + */ + _createContentView : function( content ){ + //console.debug( 'content json:', JSON.stringify( content, null, ' ' ) ); + var contentView = null, + ContentClass = this._getContentClass( content ); + //console.debug( 'content.object json:', JSON.stringify( content.object, null, ' ' ) ); + //console.debug( 'ContentClass:', ContentClass ); + //console.debug( 'content:', content ); + //console.debug( 'content.object:', content.object ); + contentView = new ContentClass({ + model : content.object, + linkTarget : this.linkTarget, + //draggable : true, + hasUser : this.hasUser, + logger : this.logger + }); + //this._setUpHdaListeners( contentView ); + return contentView; + }, + + _getContentClass : function( content ){ + switch( content.get( 'element_type' ) ){ + case 'hda': + return this.HDAViewClass; + case 'dataset_collection': + return DC_BASE.NestedDCEBaseView; + } + throw new TypeError( 'Unknown element type:', content.get( 'element_type' ) ); + }, + +// /** Set up HistoryPanel listeners for HDAView events. Currently binds: +// * HDAView#body-visible, HDAView#body-hidden to store expanded states +// * @param {HDAView} hdaView HDAView (base or edit) to listen to +// */ +// _setUpHdaListeners : function( hdaView ){ +// var panel = this; +// hdaView.on( 'error', function( model, xhr, options, msg ){ +// panel.errorHandler( model, xhr, options, msg ); +// }); +// // maintain a list of hdas whose bodies are expanded +// hdaView.on( 'body-expanded', function( model ){ +// panel.storage.addExpandedHda( model ); +// }); +// hdaView.on( 'body-collapsed', function( id ){ +// panel.storage.removeExpandedHda( id ); +// }); +// return this; +// }, + + /** attach an contentView to the panel */ + attachContentView : function( contentView, $whereTo ){ + $whereTo = $whereTo || this.$el; + var $datasetsList = this.$datasetsList( $whereTo ); + $datasetsList.append( contentView.$el ); + return this; + }, + + // ------------------------------------------------------------------------ panel events + /** event map */ + events : { + 'click .panel-navigation-back' : 'close' + }, + + /** */ + close : function( event ){ + this.$el.remove(); + this.trigger( 'collection-close' ); + }, + + // ........................................................................ misc + /** Return a string rep of the history */ + toString : function(){ + return 'CollectionPanel(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')'; + } +}); + + +//------------------------------------------------------------------------------ TEMPLATES +var _panelTemplate = [ + '<div class="history-controls">', + '<div class="panel-navigation">', + '<a class="panel-navigation-back" href="javascript:void(0)">', _l( 'Back' ), '</a>', + '</div>', + + '<div class="history-title">', + '<% if( collection.name ){ %>', + '<div class="history-name"><%= collection.hid %> : <%= collection.name %></div>', + '<% } %>', + '</div>', + + //'<div class="history-subtitle clear">', + // '<% if( history.nice_size ){ %>', + // '<div class="history-size"><%= history.nice_size %></div>', + // '<% } %>', + // '<div class="history-secondary-actions"></div>', + //'</div>', + // + //'<% if( history.deleted ){ %>', + // '<div class="warningmessagesmall"><strong>', + // _l( 'You are currently viewing a deleted history!' ), + // '</strong></div>', + //'<% } %>', + // + //'<div class="message-container">', + // '<% if( history.message ){ %>', + // // should already be localized + // '<div class="<%= history.status %>message"><%= history.message %></div>', + // '<% } %>', + //'</div>', + // + //'<div class="quota-message errormessage">', + // _l( 'You are over your disk quota' ), '. ', + // _l( 'Tool execution is on hold until your disk usage drops below your allocated quota' ), '.', + //'</div>', + // + //'<div class="tags-display"></div>', + //'<div class="annotation-display"></div>', + //'<div class="history-dataset-actions">', + // '<div class="btn-group">', + // '<button class="history-select-all-datasets-btn btn btn-default"', + // 'data-mode="select">', _l( 'All' ), '</button>', + // '<button class="history-deselect-all-datasets-btn btn btn-default"', + // 'data-mode="select">', _l( 'None' ), '</button>', + // '</div>', + // '<button class="history-dataset-action-popup-btn btn btn-default">', + // _l( 'For all selected' ), '...</button>', + //'</div>', + '</div>', + // end history controls + + // where the datasets/hdas are added + '<div class="datasets-list"></div>' + +].join( '' ); + +CollectionPanel.templates = { + panel : function( JSON ){ + return _.template( _panelTemplate, JSON, { variable: 'collection' }); + } +}; + + +//============================================================================== + return { + CollectionPanel: CollectionPanel + }; +}); diff -r 74d7ff952ec902e68cc99745fdcd3c14e86208b2 -r 598a4ec015f66237d41ed2176c673fd52b744f15 static/scripts/mvc/collection/dataset-collection-base.js --- a/static/scripts/mvc/collection/dataset-collection-base.js +++ b/static/scripts/mvc/collection/dataset-collection-base.js @@ -1,328 +1,151 @@ define([ - "mvc/history/history-content-base-view", + "mvc/base-mvc", "utils/localization" -], function( historyContentBaseView, _l ){ +], function( BASE_MVC, _l ){ /* global Backbone, LoggableMixin */ //============================================================================== -/** @class Read only view for HistoryDatasetCollectionAssociation. - * @name HDABaseView +/** @class Read only view for DatasetCollection. + * @name DCBaseView * * @augments Backbone.View * @borrows LoggableMixin#logger as #logger * @borrows LoggableMixin#log as #log * @constructs */ -var DatasetCollectionBaseView = historyContentBaseView.HistoryContentBaseView.extend({ - className : "dataset hda history-panel-hda", - id : function(){ return 'hdca-' + this.model.get( 'id' ); }, +var DCBaseView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({ + + /** logger used to record this.log messages, commonly set to console */ + // comment this out to suppress log output + //logger : console, + + /** */ + className : "dataset-collection", + /** */ + fxSpeed : 'fast', /** */ initialize : function( attributes ){ if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; } - this.log( this + '.initialize:', attributes ); - /** is the view currently in selection mode? */ - this.selectable = attributes.selectable || false; - //this.log( '\t selectable:', this.selectable ); - /** is the view currently selected? */ - this.selected = attributes.selected || false; - /** is the body of this collection view expanded/not? */ - this.expanded = attributes.expanded || false; + this.log( 'DCBaseView.initialize:', attributes ); }, - /** */ - render : function( fade ){ - var $newRender = this._buildNewRender(); - - this._queueNewRender( $newRender, fade ); - return this; - }, - - /** */ - _buildNewRender : function(){ - var $newRender = $( DatasetCollectionBaseView.templates.skeleton( this.model.toJSON() ) ); - $newRender.find( '.dataset-primary-actions' ).append( this._render_titleButtons() ); - $newRender.children( '.dataset-body' ).replaceWith( this._render_body() ); - this._setUpBehaviors( $newRender ); - return $newRender; - }, - - /** */ - _queueNewRender : function( $newRender, fade ) { - fade = ( fade === undefined )?( true ):( fade ); - var view = this; - - // fade the old render out (if desired) - if( fade ){ - $( view ).queue( function( next ){ this.$el.fadeOut( view.fxSpeed, next ); }); - } - // empty the old render, update to any new HDA state, swap in the new render contents, handle multi-select - $( view ).queue( function( next ){ - this.$el.empty() - .attr( 'class', view.className ).addClass( 'state-' + view.model.get( 'state' ) ) - .append( $newRender.children() ); - if( this.selectable ){ this.showSelector( 0 ); } - next(); - }); - // fade the new in - if( fade ){ - $( view ).queue( function( next ){ this.$el.fadeIn( view.fxSpeed, next ); }); - } - // trigger an event to know we're ready - $( view ).queue( function( next ){ - this.trigger( 'rendered', view ); - if( this.model.inReadyState() ){ - this.trigger( 'rendered:ready', view ); - } - if( this.draggable ){ this.draggableOn(); } - next(); - }); - }, - - // ................................................................................ titlebar buttons - /** Render icon-button group for the common, most easily accessed actions. - * @returns {jQuery} rendered DOM - */ - _render_titleButtons : function(){ - // render just the display for read-only - return [ ]; - }, - - // ......................................................................... state body renderers - /** Render the enclosing div of the collection body and, if expanded, the html in the body - * @returns {jQuery} rendered DOM - */ - _render_body : function(){ - var $body = $( '<div>Error: unknown state "' + this.model.get( 'state' ) + '".</div>' ), - // cheesy: get function by assumed matching name - renderFn = this[ '_render_body_' + this.model.get( 'state' ) ]; - if( _.isFunction( renderFn ) ){ - $body = renderFn.call( this ); - } - this._setUpBehaviors( $body ); - - // only render the body html if it's being shown - if( this.expanded ){ - $body.show(); - } - return $body; - }, - - /** set up js behaviors, event handlers for elements within the given container - * @param {jQuery} $container jq object that contains the elements to process (defaults to this.$el) - */ - _setUpBehaviors : function( $container ){ - $container = $container || this.$el; - // set up canned behavior on children (bootstrap, popupmenus, editable_text, etc.) - make_popup_menus( $container ); - $container.find( '[title]' ).tooltip({ placement : 'bottom' }); - }, - - // TODO: Eliminate duplication between following event map and one for HDAs. - - // ......................................................................... events - /** event map */ - events : { - // expand the body when the title is clicked or when in focus and space or enter is pressed - 'click .dataset-title-bar' : 'toggleBodyVisibility', - 'keydown .dataset-title-bar' : 'toggleBodyVisibility', - - // toggle selected state - 'click .dataset-selector' : 'toggleSelect' - }, - - /** Show or hide the body/details of history content. - * note: if the model does not have detailed data, fetch that data before showing the body - * @param {Event} event the event that triggered this (@link HDABaseView#events) - * @param {Boolean} expanded if true, expand; if false, collapse - * @fires body-expanded when a body has been expanded - * @fires body-collapsed when a body has been collapsed - */ - toggleBodyVisibility : function( event, expand ){ - // bail (with propagation) if keydown and not space or enter - var KEYCODE_SPACE = 32, KEYCODE_RETURN = 13; - if( event && ( event.type === 'keydown' ) - && !( event.keyCode === KEYCODE_SPACE || event.keyCode === KEYCODE_RETURN ) ){ - return true; - } - - var $body = this.$el.find( '.dataset-body' ); - expand = ( expand === undefined )?( !$body.is( ':visible' ) ):( expand ); - if( expand ){ - this.expandBody(); - } else { - this.collapseBody(); - } - return false; - }, - - /** Render and show the full, detailed body of this view including extra data and controls. - * @fires body-expanded when a body has been expanded - */ - expandBody : function(){ - var contentView = this; - - function _renderBodyAndExpand(){ - contentView.$el.children( '.dataset-body' ).replaceWith( contentView._render_body() ); - contentView.$el.children( '.dataset-body' ).slideDown( contentView.fxSpeed, function(){ - contentView.expanded = true; - contentView.trigger( 'body-expanded', contentView.model ); - }); - } - // TODO: Fetch more details like HDA view... - _renderBodyAndExpand(); - }, - - /** Hide the body/details of an HDA. - * @fires body-collapsed when a body has been collapsed - */ - collapseBody : function(){ - var hdaView = this; - this.$el.children( '.dataset-body' ).slideUp( hdaView.fxSpeed, function(){ - hdaView.expanded = false; - hdaView.trigger( 'body-collapsed', hdaView.model.id ); - }); - }, - - /** Render an 'ok' collection. - * @param {jQuery} parent DOM to which to append this body - */ - _render_body_ok : function(){ - // most common state renderer and the most complicated - var $body = $( DatasetCollectionBaseView.templates.body( this.model.toJSON() ) ); - - // return shortened form if del'd (no display apps or peek?) - if( this.model.get( 'deleted' ) ){ - return $body; - } - - return $body; - }, - - // ......................................................................... selection - /** display a (fa-icon) checkbox on the left of the hda that fires events when checked - * Note: this also hides the primary actions - */ - showSelector : function(){ - // make sure selected state is represented properly - if( this.selected ){ - this.select( null, true ); - } - - this.selectable = true; - this.trigger( 'selectable', true, this ); - - this.$( '.dataset-primary-actions' ).hide(); - this.$( '.dataset-selector' ).show(); - }, - - /** remove the selection checkbox */ - hideSelector : function(){ - // reverse the process from showSelect - this.selectable = false; - this.trigger( 'selectable', false, this ); - - this.$( '.dataset-selector' ).hide(); - this.$( '.dataset-primary-actions' ).show(); - }, - - toggleSelector : function(){ - if( !this.$el.find( '.dataset-selector' ).is( ':visible' ) ){ - this.showSelector(); - } else { - this.hideSelector(); - } - }, - - /** event handler for selection (also programmatic selection) - */ - select : function( event ){ - // switch icon, set selected, and trigger event - this.$el.find( '.dataset-selector span' ) - .removeClass( 'fa-square-o' ).addClass( 'fa-check-square-o' ); - if( !this.selected ){ - this.trigger( 'selected', this, event ); - this.selected = true; - } - return false; - }, - - /** event handler for clearing selection (also programmatic deselection) - */ - deselect : function( event ){ - // switch icon, set selected, and trigger event - this.$el.find( '.dataset-selector span' ) - .removeClass( 'fa-check-square-o' ).addClass( 'fa-square-o' ); - if( this.selected ){ - this.trigger( 'de-selected', this, event ); - this.selected = false; - } - return false; - }, - - toggleSelect : function( event ){ - if( this.selected ){ - this.deselect( event ); - } else { - this.select( event ); - } - }, +//TODO: render has been removed from the inheritance chain here, so this won't work when called as is // ......................................................................... misc /** String representation */ toString : function(){ var modelString = ( this.model )?( this.model + '' ):( '(no model)' ); - return 'HDCABaseView(' + modelString + ')'; + return 'DCBaseView(' + modelString + ')'; } }); -//------------------------------------------------------------------------------ TEMPLATES -//TODO: possibly break these out into a sep. module -var skeletonTemplate = _.template([ - '<div class="dataset hda">', - '<div class="dataset-warnings">', - '<% if ( collection.deleted ) { %>', - '<div class="dataset-deleted-msg warningmessagesmall"><strong>', - _l( 'This collection has been deleted.' ), +/** templates for DCBaseViews (skeleton and body) */ +DCBaseView.templates = (function(){ +// use closure to run underscore template fn only once at module load + var skeletonTemplate = _.template([ + '<div class="dataset hda">', + '<div class="dataset-warnings">', + '<% if ( collection.deleted ) { %>', + '<div class="dataset-deleted-msg warningmessagesmall"><strong>', + _l( 'This collection has been deleted.' ), + '</div>', + '<% } %>', + '<% if ( !collection.visible ) { %>', + '<div class="dataset-hidden-msg warningmessagesmall"><strong>', + _l( 'This collection has been hidden.' ), + '</div>', + '<% } %>', + '</div>', + '<div class="dataset-primary-actions"></div>', + '<div class="dataset-title-bar clear" tabindex="0">', + '<span class="dataset-state-icon state-icon"></span>', + '<div class="dataset-title">', + '<span class="dataset-name"><%- collection.name %></span>', '</div>', - '<% } %>', - '<% if ( !collection.visible ) { %>', - '<div class="dataset-hidden-msg warningmessagesmall"><strong>', - _l( 'This collection has been hidden.' ), - '</div>', - '<% } %>', - '</div>', - '<div class="dataset-selector"><span class="fa fa-2x fa-square-o"></span></div>', - '<div class="dataset-primary-actions"></div>', - '<div class="dataset-title-bar clear" tabindex="0">', - '<span class="dataset-state-icon state-icon"></span>', - '<div class="dataset-title">', - '<span class="hda-hid"><%= collection.hid %></span> ', - '<span class="dataset-name"><%= collection.name %></span>', '</div>', - '</div>', - '<div class="dataset-body"></div>', - '</div>' -].join( '' )); + '<div class="dataset-body"></div>', + '</div>' + ].join( '' )); -var bodyTemplate = _.template([ - '<div class="dataset-body">', - '<div class="dataset-summary">', - _l( 'A dataset collection.' ), - '</div>' -].join( '' )); + var bodyTemplate = _.template([ + '<div class="dataset-body">', + '<div class="dataset-summary">', + _l( 'A dataset collection.' ), + '</div>' + ].join( '' )); -DatasetCollectionBaseView.templates = { // we override here in order to pass the localizer (_L) into the template scope - since we use it as a fn within - skeleton : function( collectionJSON ){ - return skeletonTemplate({ _l: _l, collection: collectionJSON }); - }, - body : function( collectionJSON ){ - return bodyTemplate({ _l: _l, collection: collectionJSON }); - } -}; + return { + skeleton : function( collectionJSON ){ + return skeletonTemplate({ _l: _l, collection: collectionJSON }); + }, + body : function( collectionJSON ){ + return bodyTemplate({ _l: _l, collection: collectionJSON }); + } + }; +}()); + + +//TODO: unused +////============================================================================== +///** @class Read only view for DatasetCollectionElement. +// * @name DCEBaseView +// * +// * @augments Backbone.View +// * @borrows LoggableMixin#logger as #logger +// * @borrows LoggableMixin#log as #log +// * @constructs +// */ +//var NestedDCBaseView = DCBaseView.extend({ +// +// logger : console, +// +// /** */ +// initialize : function( attributes ){ +// if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; } +// this.warn( this + '.initialize:', attributes ); +// DCBaseView.prototype.initialize.call( this, attributes ); +// }, +// +// _template : function(){ +// this.debug( this.model ); +// this.debug( this.model.toJSON() ); +// return NestedDCBaseView.templates.skeleton( this.model.toJSON() ); +// }, +// +// // ......................................................................... misc +// /** String representation */ +// toString : function(){ +// var modelString = ( this.model )?( this.model + '' ):( '(no model)' ); +// return 'NestedDCBaseView(' + modelString + ')'; +// } +//}); +// +////------------------------------------------------------------------------------ TEMPLATES +////TODO: possibly break these out into a sep. module +//NestedDCBaseView.templates = (function(){ +// var skeleton = _.template([ +// '<div class="dataset hda history-panel-hda state-ok">', +// '<div class="dataset-primary-actions"></div>', +// '<div class="dataset-title-bar clear" tabindex="0">', +// '<div class="dataset-title">', +// '<span class="dataset-name"><%= collection.name %></span>', +// '</div>', +// '</div>', +// '</div>' +// ].join( '' )); +// // we override here in order to pass the localizer (_L) into the template scope - since we use it as a fn within +// return { +// skeleton : function( json ){ +// return skeleton({ _l: _l, collection: json }); +// } +// }; +//}()); + //============================================================================== return { - DatasetCollectionBaseView : DatasetCollectionBaseView + DCBaseView : DCBaseView, + //NestedDCBaseView : NestedDCBaseView, }; }); diff -r 74d7ff952ec902e68cc99745fdcd3c14e86208b2 -r 598a4ec015f66237d41ed2176c673fd52b744f15 static/scripts/mvc/collection/dataset-collection-edit.js --- a/static/scripts/mvc/collection/dataset-collection-edit.js +++ b/static/scripts/mvc/collection/dataset-collection-edit.js @@ -1,75 +1,36 @@ define([ - "mvc/dataset/hda-model", + "mvc/dataset/states", "mvc/collection/dataset-collection-base", "utils/localization" -], function( hdaModel, datasetCollectionBase, _l ){ +], function( STATES, DC_BASE_VIEW, _l ){ //============================================================================== -/** @class Editing view for HistoryDatasetCollectionAssociation. +var _super = DC_BASE_VIEW.DCBaseView; +/** @class Editing view for DatasetCollection. * @name DatasetCollectionEditView * - * @augments DatasetCollectionBaseView + * @augments DCBaseView * @constructs */ -var DatasetCollectionEditView = datasetCollectionBase.DatasetCollectionBaseView.extend( { +var DCEditView = _super.extend({ + + /** logger used to record this.log messages, commonly set to console */ + // comment this out to suppress log output + //logger : console, initialize : function( attributes ){ - datasetCollectionBase.DatasetCollectionBaseView.prototype.initialize.call( this, attributes ); - }, - - // ......................................................................... edit attr, delete - /** Render icon-button group for the common, most easily accessed actions. - * Overrides _render_titleButtons to include editing related buttons. - * @see DatasetCollectionBaseView#_render_titleButtons - * @returns {jQuery} rendered DOM - */ - _render_titleButtons : function(){ - // render the display, edit attr and delete icon-buttons - return datasetCollectionBase.DatasetCollectionBaseView.prototype._render_titleButtons.call( this ).concat([ - this._render_deleteButton() - ]); - }, - - /** Render icon-button to delete this hda. - * @returns {jQuery} rendered DOM - */ - _render_deleteButton : function(){ - // don't show delete if... - if( ( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.NEW ) - || ( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.NOT_VIEWABLE ) - || ( !this.model.get( 'accessible' ) ) ){ - return null; - } - - var self = this, - deleteBtnData = { - title : _l( 'Delete' ), - classes : 'dataset-delete', - onclick : function() { - // ...bler... tooltips being left behind in DOM (hover out never called on deletion) - self.$el.find( '.icon-btn.dataset-delete' ).trigger( 'mouseout' ); - self.model[ 'delete' ](); - } - }; - if( this.model.get( 'deleted' ) ){ - deleteBtnData = { - title : _l( 'Dataset collection is already deleted' ), - disabled : true - }; - } - deleteBtnData.faIcon = 'fa-times'; - return faIconButton( deleteBtnData ); + _super.prototype.initialize.call( this, attributes ); }, // ......................................................................... misc /** string rep */ toString : function(){ var modelString = ( this.model )?( this.model + '' ):( '(no model)' ); - return 'HDCAEditView(' + modelString + ')'; + return 'DCEditView(' + modelString + ')'; } }); //============================================================================== return { - DatasetCollectionEditView : DatasetCollectionEditView + DCEditView : DCEditView }; }); diff -r 74d7ff952ec902e68cc99745fdcd3c14e86208b2 -r 598a4ec015f66237d41ed2176c673fd52b744f15 static/scripts/mvc/collection/hdca-base.js --- /dev/null +++ b/static/scripts/mvc/collection/hdca-base.js @@ -0,0 +1,116 @@ +define([ + "mvc/base-mvc", + "mvc/collection/dataset-collection-base", + "mvc/history/history-content-base-view", + "utils/localization" +], function( BASE_MVC, DC_BASE, HISTORY_CONTENT_BASE_VIEW, _l ){ +/* global Backbone, LoggableMixin */ +//============================================================================== +var HCVMixin = HISTORY_CONTENT_BASE_VIEW.HistoryContentViewMixin, + _super = DC_BASE.DCBaseView; +/** @class Read only view for HistoryDatasetCollectionAssociation. + * @name + * + * @augments Backbone.View + * @borrows LoggableMixin#logger as #logger + * @borrows LoggableMixin#log as #log + * @constructs + */ +var HDCABaseView = _super.extend( BASE_MVC.mixin( HCVMixin, { + + /** logger used to record this.log messages, commonly set to console */ + // comment this out to suppress log output + //logger : console, + + /** */ + className : "dataset hda history-panel-hda", + + /** */ + initialize : function( attributes ){ + if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; } + this.log( this + '.initialize:', attributes ); + _super.prototype.initialize.call( this, attributes ); + HCVMixin.initialize.call( this, attributes ); + }, + + /** */ + _template : function(){ + return HDCABaseView.templates.skeleton( this.model.toJSON() ); + }, + + /** */ + events : _.extend( _.clone( HCVMixin.events ), { + }), + + /** */ + _renderBody : function(){ + // override this + var $body = $( _super.templates.body( this.model.toJSON() ) ); + if( this.expanded ){ + $body.show(); + } + return $body; + }, + + // ......................................................................... misc + /** String representation */ + toString : function(){ + var modelString = ( this.model )?( this.model + '' ):( '(no model)' ); + return 'HDCABaseView(' + modelString + ')'; + } +})); + +/** templates for HDCAs (skeleton and body) */ +HDCABaseView.templates = HDCABaseView.prototype.templates = (function(){ +// use closure to run underscore template fn only once at module load + var skeletonTemplate = _.template([ + '<div class="dataset hda">', + '<div class="dataset-warnings">', + '<% if ( collection.deleted ) { %>', + '<div class="dataset-deleted-msg warningmessagesmall"><strong>', + _l( 'This collection has been deleted.' ), + '</div>', + '<% } %>', + '<% if ( !collection.visible ) { %>', + '<div class="dataset-hidden-msg warningmessagesmall"><strong>', + _l( 'This collection has been hidden.' ), + '</div>', + '<% } %>', + '</div>', + '<div class="dataset-selector"><span class="fa fa-2x fa-square-o"></span></div>', + '<div class="dataset-primary-actions"></div>', + '<div class="dataset-title-bar clear" tabindex="0">', + '<span class="dataset-state-icon state-icon"></span>', + '<div class="dataset-title">', + '<span class="hda-hid"><%- collection.hid %></span> ', + '<span class="dataset-name"><%- collection.name %></span>', + '</div>', + '</div>', + '<div class="dataset-body"></div>', + '</div>' + ].join( '' )); + + var bodyTemplate = _.template([ + '<div class="dataset-body">', + '<div class="dataset-summary">', + _l( 'A dataset collection.' ), + '</div>' + ].join( '' )); + + // we override here in order to pass the localizer (_L) into the template scope - since we use it as a fn within + return { + skeleton : function( collectionJSON ){ + return skeletonTemplate({ _l: _l, collection: collectionJSON }); + }, + body : function( collectionJSON ){ + return bodyTemplate({ _l: _l, collection: collectionJSON }); + } + }; +}()); + + +//============================================================================== + return { + HDCABaseView : HDCABaseView + }; +}); diff -r 74d7ff952ec902e68cc99745fdcd3c14e86208b2 -r 598a4ec015f66237d41ed2176c673fd52b744f15 static/scripts/mvc/collection/hdca-edit.js --- /dev/null +++ b/static/scripts/mvc/collection/hdca-edit.js @@ -0,0 +1,74 @@ +define([ + "mvc/dataset/states", + "mvc/collection/hdca-base", + "utils/localization" +], function( STATES, HDCA_BASE, _l ){ +//============================================================================== +var _super = HDCA_BASE.HDCABaseView; +/** @class Editing view for HistoryDatasetCollectionAssociation. + * @name DatasetCollectionEditView + * + * @augments HDCABaseView + * @constructs + */ +var HDCAEditView = _super.extend({ + + /** logger used to record this.log messages, commonly set to console */ + // comment this out to suppress log output + //logger : console, + + initialize : function( attributes ){ + _super.prototype.initialize.call( this, attributes ); + }, + + // ......................................................................... edit attr, delete + /** Render icon-button group for the common, most easily accessed actions. + * Overrides _render_titleButtons to include editing related buttons. + * @returns {jQuery} rendered DOM + */ + _render_titleButtons : function(){ + this.log( this + '._render_titleButtons' ); + // render the display, edit attr and delete icon-buttons + return _super.prototype._render_titleButtons.call( this ) + .concat([ + this._render_deleteButton() + ]); + }, + + /** Render icon-button to delete this hda. + * @returns {jQuery} rendered DOM + */ + _render_deleteButton : function(){ + var self = this, + deleteBtnData = { + title : _l( 'Delete' ), + classes : 'dataset-delete', + onclick : function() { + // ...bler... tooltips being left behind in DOM (hover out never called on deletion) + self.$el.find( '.icon-btn.dataset-delete' ).trigger( 'mouseout' ); + self.model[ 'delete' ](); + } + }; + if( self.model.get( 'deleted' ) ){ + deleteBtnData = { + title : _l( 'Dataset collection is already deleted' ), + disabled : true + }; + } + deleteBtnData.faIcon = 'fa-times'; + return faIconButton( deleteBtnData ); + }, + + // ......................................................................... misc + /** string rep */ + toString : function(){ + var modelString = ( this.model )?( this.model + '' ):( '(no model)' ); + return 'HDCAEditView(' + modelString + ')'; + } +}); + +//============================================================================== + return { + HDCAEditView : HDCAEditView + }; +}); diff -r 74d7ff952ec902e68cc99745fdcd3c14e86208b2 -r 598a4ec015f66237d41ed2176c673fd52b744f15 static/scripts/mvc/collection/hdca-model.js --- a/static/scripts/mvc/collection/hdca-model.js +++ b/static/scripts/mvc/collection/hdca-model.js @@ -1,69 +1,89 @@ define([ "mvc/history/history-content-base", + "mvc/collection/collection-model", "utils/localization" -], function( historyContent, _l ){ +], function( HISTORY_CONTENT, DC_MODEL, _l ){ //============================================================================== -var HistoryDatasetCollectionAssociation = historyContent.HistoryContent.extend( -/** @lends HistoryDatasetCollectionAssociation.prototype */{ - /** default attributes for a model */ - defaults : { - // parent (containing) history - history_id : null, - // often used with tagging - model_class : 'HistoryDatasetCollectionAssociation', - history_content_type : 'dataset_collection', - hid : 0, +var hcontentMixin = HISTORY_CONTENT.HistoryContentMixin, +/** @class Backbone model for (generic) Dataset Collection within a History. + * @constructs + */ + HistoryDatasetCollection = DC_MODEL.DatasetCollection.extend( hcontentMixin ); - id : null, - name : '(unnamed dataset collection)', - // one of HistoryDatasetAssociation.STATES, calling them all 'ok' for now. - state : 'ok', - accessible : true, - deleted : false, - visible : true, +//NOTE: the following prototypes may not be necessary - but I wanted to specifiy +// them (for now) and allow for the possibility of unique functionality +//============================================================================== +var ListDC = DC_MODEL.ListDatasetCollection, +/** @class Backbone model for List Dataset Collection within a History. + * @constructs + */ + HistoryListDatasetCollection = ListDC.extend( hcontentMixin ).extend( +/** @lends HistoryListDatasetCollection.prototype */{ - purged : false, // Purged doesn't make sense for collections - at least right now. - - tags : [], - annotation : '' - }, - urls : function(){ + initialize : function( model, options ){ + ListDC.prototype.initialize.call( this, model, options ); + hcontentMixin.initialize.call( this, model, options ); + //TODO: in lieu of any state info for collections, show as 'ok' + this.set( 'state', 'ok', { silent: true }); }, - inReadyState : function(){ - return true; // TODO - }, - - // ........................................................................ search - /** what attributes of an collection will be used in a text search */ - searchAttributes : [ - 'name' - ], - - /** our attr keys don't often match the labels we display to the user - so, when using - * attribute specifiers ('name="bler"') in a term, allow passing in aliases for the - * following attr keys. - */ - searchAliases : { - title : 'name' - // TODO: Add tag... - }, - - // ........................................................................ misc - /** String representation */ + /** String representation. */ toString : function(){ - var nameAndId = this.get( 'id' ) || ''; - if( this.get( 'name' ) ){ - nameAndId = this.get( 'hid' ) + ' :"' + this.get( 'name' ) + '",' + nameAndId; - } - return 'HDCA-' + this.get( 'collection_type' ) + '(' + nameAndId + ')'; + return ([ 'HistoryListDatasetCollection(', this.get( 'name' ), ')' ].join( '' )); } }); //============================================================================== +var PairDC = DC_MODEL.PairDatasetCollection, +/** @class Backbone model for Pair Dataset Collection within a History. + * @constructs + */ + HistoryPairDatasetCollection = PairDC.extend( hcontentMixin ).extend( +/** @lends HistoryPairDatasetCollection.prototype */{ + + initialize : function( model, options ){ + PairDC.prototype.initialize.call( this, model, options ); + hcontentMixin.initialize.call( this, model, options ); + //TODO: in lieu of any state info for collections, show as 'ok' + this.set( 'state', 'ok', { silent: true }); + }, + + /** String representation. */ + toString : function(){ + return ([ 'HistoryPairDatasetCollection(', this.get( 'name' ), ')' ].join( '' )); + } +}); + + +//============================================================================== +var ListPairedDC = DC_MODEL.ListPairedDatasetCollection, +/** @class Backbone model for List of Pairs Dataset Collection within a History. + * @constructs + */ + HistoryListPairedDatasetCollection = ListPairedDC.extend( hcontentMixin ).extend( +/** @lends HistoryListPairedDatasetCollection.prototype */{ + + initialize : function( model, options ){ + ListPairedDC.prototype.initialize.call( this, model, options ); + hcontentMixin.initialize.call( this, model, options ); + //TODO: in lieu of any state info for collections, show as 'ok' + this.set( 'state', 'ok', { silent: true }); + }, + + /** String representation. */ + toString : function(){ + return ([ 'HistoryListPairedDatasetCollection(', this.get( 'name' ), ')' ].join( '' )); + } +}); + + +//============================================================================== return { - HistoryDatasetCollectionAssociation : HistoryDatasetCollectionAssociation + HistoryDatasetCollection : HistoryDatasetCollection, + HistoryListDatasetCollection : HistoryListDatasetCollection, + HistoryPairDatasetCollection : HistoryPairDatasetCollection, + HistoryListPairedDatasetCollection : HistoryListPairedDatasetCollection }; }); diff -r 74d7ff952ec902e68cc99745fdcd3c14e86208b2 -r 598a4ec015f66237d41ed2176c673fd52b744f15 static/scripts/mvc/dataset/hda-base.js --- a/static/scripts/mvc/dataset/hda-base.js +++ b/static/scripts/mvc/dataset/hda-base.js @@ -1,11 +1,12 @@ define([ - "mvc/dataset/hda-model", + "mvc/dataset/states", "mvc/history/history-content-base-view", "mvc/data", "utils/localization" -], function( hdaModel, historyContentBaseView, dataset, _l ){ +], function( STATES, HCONTENT_BASE_VIEW, DATA, _l ){ /* global Backbone */ //============================================================================== +var _super = HCONTENT_BASE_VIEW.HistoryContentBaseView; /** @class Read only view for HistoryDatasetAssociation. * @name HDABaseView * @@ -14,11 +15,11 @@ * @borrows LoggableMixin#log as #log * @constructs */ -var HDABaseView = historyContentBaseView.HistoryContentBaseView.extend( +var HDABaseView = _super.extend( /** @lends HDABaseView.prototype */{ - ///** logger used to record this.log messages, commonly set to console */ - //// comment this out to suppress log output + /** logger used to record this.log messages, commonly set to console */ + // comment this out to suppress log output //logger : console, tagName : "div", @@ -38,6 +39,8 @@ if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; } this.log( this + '.initialize:', attributes ); + _super.prototype.initialize.call( this, attributes ); + /** list of rendering functions for the default, primary icon-buttons. */ this.defaultPrimaryActionButtonRenderers = [ this._render_showParamsButton @@ -46,15 +49,6 @@ /** where should pages from links be displayed? (default to new tab/window) */ this.linkTarget = attributes.linkTarget || '_blank'; - /** is the view currently in selection mode? */ - this.selectable = attributes.selectable || false; - //this.log( '\t selectable:', this.selectable ); - /** is the view currently selected? */ - this.selected = attributes.selected || false; - //this.log( '\t selected:', this.selected ); - /** is the body of this hda view expanded/not? */ - this.expanded = attributes.expanded || false; - //this.log( '\t expanded:', this.expanded ); /** is the body of this hda view expanded/not? */ this.draggable = attributes.draggable || false; //this.log( '\t draggable:', this.draggable ); @@ -86,77 +80,20 @@ // ......................................................................... render main /** Render this HDA, set up ui. * @param {Boolean} fade whether or not to fade out/in when re-rendering - * @fires rendered when rendered - * @fires rendered:ready when first rendered and NO running HDAs * @returns {Object} this HDABaseView */ render : function( fade ){ //HACK: hover exit doesn't seem to be called on prev. tooltips when RE-rendering - so: no tooltip hide // handle that here by removing previous view's tooltips this.$el.find("[title]").tooltip( "destroy" ); - // re-get web controller urls for functions relating to this hda. (new model data may have changed this) this.urls = this.model.urls(); - var $newRender = this._buildNewRender(); - this._queueNewRender( $newRender, fade ); - return this; - + return _super.prototype.render.call( this, fade ); }, - _buildNewRender : function(){ - // create a new render using a skeleton template, render title buttons, render body, and set up events, etc. - var $newRender = $( HDABaseView.templates.skeleton( this.model.toJSON() ) ); - $newRender.find( '.dataset-primary-actions' ).append( this._render_titleButtons() ); - $newRender.children( '.dataset-body' ).replaceWith( this._render_body() ); - this._setUpBehaviors( $newRender ); - //this._renderSelectable( $newRender ); - return $newRender; - }, - - _queueNewRender : function( $newRender, fade ) { - fade = ( fade === undefined )?( true ):( fade ); - var view = this; - - // fade the old render out (if desired) - if( fade ){ - $( view ).queue( function( next ){ this.$el.fadeOut( view.fxSpeed, next ); }); - } - // empty the old render, update to any new HDA state, swap in the new render contents, handle multi-select - $( view ).queue( function( next ){ - this.$el.empty() - .attr( 'class', view.className ).addClass( 'state-' + view.model.get( 'state' ) ) - .append( $newRender.children() ); - if( this.selectable ){ this.showSelector( 0 ); } - next(); - }); - // fade the new in - if( fade ){ - $( view ).queue( function( next ){ this.$el.fadeIn( view.fxSpeed, next ); }); - } - // trigger an event to know we're ready - $( view ).queue( function( next ){ - this.trigger( 'rendered', view ); - if( this.model.inReadyState() ){ - this.trigger( 'rendered:ready', view ); - } - if( this.draggable ){ this.draggableOn(); } - next(); - }); - }, - - /** set up js behaviors, event handlers for elements within the given container - * @param {jQuery} $container jq object that contains the elements to process (defaults to this.$el) - */ - _setUpBehaviors : function( $container ){ - $container = $container || this.$el; - // set up canned behavior on children (bootstrap, popupmenus, editable_text, etc.) - make_popup_menus( $container ); - $container.find( '[title]' ).tooltip({ placement : 'bottom' }); - }, - // ................................................................................ titlebar buttons - /** Render icon-button group for the common, most easily accessed actions. + /** In this override, render the dataset display button * @returns {jQuery} rendered DOM */ _render_titleButtons : function(){ @@ -170,8 +107,8 @@ _render_displayButton : function(){ // don't show display if not viewable or not accessible // (do show if in error, running) - if( ( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.NOT_VIEWABLE ) - || ( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.DISCARDED ) + if( ( this.model.get( 'state' ) === STATES.NOT_VIEWABLE ) + || ( this.model.get( 'state' ) === STATES.DISCARDED ) || ( !this.model.get( 'accessible' ) ) ){ return null; } @@ -187,12 +124,12 @@ displayBtnData.title = _l( 'Cannot display datasets removed from disk' ); // disable if still uploading - } else if( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.UPLOAD ){ + } else if( this.model.get( 'state' ) === STATES.UPLOAD ){ displayBtnData.disabled = true; displayBtnData.title = _l( 'This dataset must finish uploading before it can be viewed' ); // disable if still new - } else if( this.model.get( 'state' ) === hdaModel.HistoryDatasetAssociation.STATES.NEW ){ + } else if( this.model.get( 'state' ) === STATES.NEW ){ displayBtnData.disabled = true; displayBtnData.title = _l( 'This dataset is not yet viewable' ); @@ -211,9 +148,9 @@ title : "Data Viewer: " + self.model.get( 'name' ), type : "other", content : function( parent_elt ){ - var new_dataset = new dataset.TabularDataset({ id: self.model.get( 'id' ) }); + var new_dataset = new DATA.TabularDataset({ id: self.model.get( 'id' ) }); $.when( new_dataset.fetch() ).then( function(){ - dataset.createTabularDatasetChunkedView({ + DATA.createTabularDatasetChunkedView({ model: new_dataset, parent_elt: parent_elt, embedded: true, @@ -298,7 +235,7 @@ /** Render the enclosing div of the hda body and, if expanded, the html in the body * @returns {jQuery} rendered DOM */ - _render_body : function(){ + _renderBody : function(){ var $body = $( '<div>Error: unknown dataset state "' + this.model.get( 'state' ) + '".</div>' ), // cheesy: get function by assumed matching name renderFn = this[ '_render_body_' + this.model.get( 'state' ) ]; @@ -437,61 +374,23 @@ // ......................................................................... events /** event map */ - events : { - // expand the body when the title is clicked or when in focus and space or enter is pressed - 'click .dataset-title-bar' : 'toggleBodyVisibility', - 'keydown .dataset-title-bar' : 'toggleBodyVisibility', + events : _.extend( _.clone( _super.prototype.events ), { + }), - // dragging - don't work, originalEvent === null - //'dragstart .dataset-title-bar' : 'dragStartHandler', - //'dragend .dataset-title-bar' : 'dragEndHandler' - - // toggle selected state - 'click .dataset-selector' : 'toggleSelect' - }, - - /** Show or hide the body/details of history content. - * note: if the model does not have detailed data, fetch that data before showing the body - * @param {Event} event the event that triggered this (@link HDABaseView#events) - * @param {Boolean} expanded if true, expand; if false, collapse - * @fires body-expanded when a body has been expanded - * @fires body-collapsed when a body has been collapsed + /** Override for expanding hda details (within the panel) + * note: in this override, the fetch for details does *not* fire a change event (silent == true) + * @fires expanded when a body has been expanded */ - toggleBodyVisibility : function( event, expand ){ - // bail (with propagation) if keydown and not space or enter - var KEYCODE_SPACE = 32, KEYCODE_RETURN = 13; - if( event && ( event.type === 'keydown' ) - && !( event.keyCode === KEYCODE_SPACE || event.keyCode === KEYCODE_RETURN ) ){ - return true; - } - - var $body = this.$el.find( '.dataset-body' ); - expand = ( expand === undefined )?( !$body.is( ':visible' ) ):( expand ); - if( expand ){ - this.expandBody(); - } else { - this.collapseBody(); - } - return false; - }, - - /** Render and show the full, detailed body of this view including extra data and controls. - * @fires body-expanded when a body has been expanded - */ - expandBody : function(){ + expand : function(){ var hdaView = this; function _renderBodyAndExpand(){ - hdaView.$el.children( '.dataset-body' ).replaceWith( hdaView._render_body() ); + hdaView.$el.children( '.dataset-body' ).replaceWith( hdaView._renderBody() ); + //NOTE: needs to be set after the above or the slide will not show + hdaView.expanded = true; hdaView.$el.children( '.dataset-body' ).slideDown( hdaView.fxSpeed, function(){ - hdaView.expanded = true; - hdaView.trigger( 'body-expanded', hdaView.model ); + hdaView.trigger( 'expanded', hdaView.model ); }); - - //hdaView.render( false ).$el.children( '.dataset-body' ).slideDown( hdaView.fxSpeed, function(){ - // hdaView.expanded = true; - // hdaView.trigger( 'body-expanded', hdaView.model.get( 'id' ) ); - //}); } // fetch first if no details in the model if( this.model.inReadyState() && !this.model.hasDetails() ){ @@ -505,128 +404,6 @@ } }, - /** Hide the body/details of an HDA. - * @fires body-collapsed when a body has been collapsed - */ - collapseBody : function(){ - var hdaView = this; - this.$el.children( '.dataset-body' ).slideUp( hdaView.fxSpeed, function(){ - hdaView.expanded = false; - hdaView.trigger( 'body-collapsed', hdaView.model.id ); - }); - }, - - // ......................................................................... selection - /** display a (fa-icon) checkbox on the left of the hda that fires events when checked - * Note: this also hides the primary actions - */ - showSelector : function(){ - // make sure selected state is represented properly - if( this.selected ){ - this.select( null, true ); - } - - this.selectable = true; - this.trigger( 'selectable', true, this ); - - this.$( '.dataset-primary-actions' ).hide(); - this.$( '.dataset-selector' ).show(); - }, - - /** remove the selection checkbox */ - hideSelector : function(){ - // reverse the process from showSelect - this.selectable = false; - this.trigger( 'selectable', false, this ); - - this.$( '.dataset-selector' ).hide(); - this.$( '.dataset-primary-actions' ).show(); - }, - - toggleSelector : function(){ - if( !this.$el.find( '.dataset-selector' ).is( ':visible' ) ){ - this.showSelector(); - } else { - this.hideSelector(); - } - }, - - /** event handler for selection (also programmatic selection) - */ - select : function( event ){ - // switch icon, set selected, and trigger event - this.$el.find( '.dataset-selector span' ) - .removeClass( 'fa-square-o' ).addClass( 'fa-check-square-o' ); - if( !this.selected ){ - this.trigger( 'selected', this, event ); - this.selected = true; - } - return false; - }, - - /** event handler for clearing selection (also programmatic deselection) - */ - deselect : function( event ){ - // switch icon, set selected, and trigger event - this.$el.find( '.dataset-selector span' ) - .removeClass( 'fa-check-square-o' ).addClass( 'fa-square-o' ); - if( this.selected ){ - this.trigger( 'de-selected', this, event ); - this.selected = false; - } - return false; - }, - - toggleSelect : function( event ){ - if( this.selected ){ - this.deselect( event ); - } else { - this.select( event ); - } - }, - - // ......................................................................... drag/drop - draggableOn : function(){ - this.draggable = true; - //TODO: I have no idea why this doesn't work with the events hash or jq.on()... - //this.$el.find( '.dataset-title-bar' ) - // .attr( 'draggable', true ) - // .bind( 'dragstart', this.dragStartHandler, false ) - // .bind( 'dragend', this.dragEndHandler, false ); - this.dragStartHandler = _.bind( this._dragStartHandler, this ); - this.dragEndHandler = _.bind( this._dragEndHandler, this ); - - var titleBar = this.$el.find( '.dataset-title-bar' ).attr( 'draggable', true ).get(0); - titleBar.addEventListener( 'dragstart', this.dragStartHandler, false ); - titleBar.addEventListener( 'dragend', this.dragEndHandler, false ); - }, - draggableOff : function(){ - this.draggable = false; - var titleBar = this.$el.find( '.dataset-title-bar' ).attr( 'draggable', false ).get(0); - titleBar.removeEventListener( 'dragstart', this.dragStartHandler, false ); - titleBar.removeEventListener( 'dragend', this.dragEndHandler, false ); - }, - toggleDraggable : function(){ - if( this.draggable ){ - this.draggableOff(); - } else { - this.draggableOn(); - } - }, - _dragStartHandler : function( event ){ - //console.debug( 'dragStartHandler:', this, event, arguments ) - this.trigger( 'dragstart', this ); - event.dataTransfer.effectAllowed = 'move'; - //TODO: all except IE: should be 'application/json', IE: must be 'text' - event.dataTransfer.setData( 'text', JSON.stringify( this.model.toJSON() ) ); - return false; - }, - _dragEndHandler : function( event ){ - this.trigger( 'dragend', this ); - //console.debug( 'dragEndHandler:', event ) - return false; - }, - // ......................................................................... removal /** Remove this view's html from the DOM and remove all event listeners. * @param {Function} callback an optional function called when removal is done @@ -799,7 +576,7 @@ '</div>' ].join( '' )); -HDABaseView.templates = { +HDABaseView.templates = HDABaseView.prototype.templates = { // we override here in order to pass the localizer (_L) into the template scope - since we use it as a fn within skeleton : function( hdaJSON ){ return skeletonTemplate({ _l: _l, hda: hdaJSON }); This diff is so big that we needed to truncate the remainder. Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.