commit/galaxy-central: carlfeberhard: History panel: make search input shown by default, remove toggle; List panel: make collection-panel and history-panel inherit from list-panel, move list-panel and base-mvc.list-item to mvc/list; general history and collection panel refactoring
1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/b0c8da2b3c1e/ Changeset: b0c8da2b3c1e User: carlfeberhard Date: 2014-09-08 22:49:30 Summary: History panel: make search input shown by default, remove toggle; List panel: make collection-panel and history-panel inherit from list-panel, move list-panel and base-mvc.list-item to mvc/list; general history and collection panel refactoring Affected #: 48 files diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/base-mvc.js --- a/static/scripts/mvc/base-mvc.js +++ b/static/scripts/mvc/base-mvc.js @@ -42,6 +42,7 @@ if( this.logger ){ var log = this.logger.log; if( typeof this.logger.log === 'object' ){ +//TODO:! there has to be a way to get the lineno/file into this log = Function.prototype.bind.call( this.logger.log, this.logger ); } return log.apply( this.logger, arguments ); @@ -149,6 +150,23 @@ //============================================================================== +/** Function that allows mixing of hashs into bbone MVC while showing the mixins first + * (before the more local class overrides/hash). + * Basically, a simple reversal of param order on _.defaults() - to show mixins in top of definition. + * @example: + * var NewModel = Something.extend( mixin( MyMixinA, MyMixinB, { ... myVars : ... }) ); + * + * NOTE: this does not combine any hashes (like events, etc.) and you're expected to handle that + */ +function mixin( mixinHash1, /* mixinHash2, etc: ... variadic */ propsHash ){ + var args = Array.prototype.slice.call( arguments, 0 ), + lastArg = args.pop(); + args.unshift( lastArg ); + return _.defaults.apply( _, args ); +} + + +//============================================================================== /** A mixin for models that allow T/F/Matching to their attributes - useful when * searching or filtering collections of models. * @example: @@ -329,205 +347,6 @@ //============================================================================== -/** Function that allows mixing of hashs into bbone MVC while showing the mixins first - * (before the more local class overrides/hash). - * Basically, a simple reversal of param order on _.defaults() - to show mixins in top of definition. - * @example: - * var NewModel = Something.extend( mixin( MyMixinA, MyMixinB, { ... myVars : ... }) ); - * - * NOTE: this does not combine any hashes (like events, etc.) and you're expected to handle that - */ -function mixin( mixinHash1, /* mixinHash2, etc: ... variadic */ propsHash ){ - var args = Array.prototype.slice.call( arguments, 0 ), - lastArg = args.pop(); - args.unshift( lastArg ); - return _.defaults.apply( _, args ); -} - -//============================================================================== -/** Return an underscore template fn from an array of strings. - * @param {String[]} template the template strings to compile into the underscore template fn - * @param {String} jsonNamespace an optional namespace for the json data passed in (defaults to 'model') - * @returns {Function} the (wrapped) underscore template fn - * The function accepts: - * - * The template strings can access: - * the json/model hash using model ("<%- model.myAttr %>) using the jsonNamespace above - * _l: the localizer function - * view (if passed): ostensibly, the view using the template (handy for view instance vars) - * Because they're namespaced, undefined attributes will not throw an error. - * - * @example: - * templateBler : BASE_MVC.wrapTemplate([ - * '<div class="myclass <%- mynamespace.modelClass %>">', - * '<span><% print( _l( mynamespace.message ) ); %>:<%= view.status %></span>' - * '</div>' - * ], 'mynamespace' ) - * - * Meant to be called in a View's definition in order to compile only once. - * - */ -function wrapTemplate( template, jsonNamespace ){ - jsonNamespace = jsonNamespace || 'model'; - var templateFn = _.template( template.join( '' ) ); - return function( json, view ){ - var templateVars = { view : view || {}, _l : _l }; - templateVars[ jsonNamespace ] = json || {}; - return templateFn( templateVars ); - }; -} - -//============================================================================== -/** A view which, when first rendered, shows only summary data/attributes, but - * can be expanded to show further details (and optionally fetch those - * details from the server). - */ -var ExpandableView = Backbone.View.extend( LoggableMixin ).extend({ -//TODO: Although the reasoning behind them is different, this shares a lot with HiddenUntilActivated above: combine them - //PRECONDITION: model must have method hasDetails - //PRECONDITION: subclasses must have templates.el and templates.details - - initialize : function( attributes ){ - /** are the details of this view expanded/shown or not? */ - this.expanded = attributes.expanded || false; - //this.log( '\t expanded:', this.expanded ); - this.fxSpeed = attributes.fxSpeed || this.fxSpeed; - }, - - // ........................................................................ render main - /** jq fx speed */ - fxSpeed : 'fast', - - /** Render this content, set up ui. - * @param {Number or String} speed the speed of the render - */ - render : function( speed ){ - var $newRender = this._buildNewRender(); - this._setUpBehaviors( $newRender ); - this._queueNewRender( $newRender, speed ); - return this; - }, - - /** Build a temp div containing the new children for the view's $el. - * If the view is already expanded, build the details as well. - */ - _buildNewRender : function(){ - // create a new render using a skeleton template, render title buttons, render body, and set up events, etc. - var $newRender = $( this.templates.el( this.model.toJSON(), this ) ); - if( this.expanded ){ - this.$details( $newRender ).replaceWith( this._renderDetails().show() ); - } - return $newRender; - }, - - /** Fade out the old el, swap in the new contents, then fade in. - * @param {Number or String} speed jq speed to use for rendering effects - * @fires rendered when rendered - */ - _queueNewRender : function( $newRender, speed ) { - speed = ( speed === undefined )?( this.fxSpeed ):( speed ); - var view = this; - - $( view ).queue( 'fx', [ - function( next ){ this.$el.fadeOut( speed, next ); }, - function( next ){ - view._swapNewRender( $newRender ); - next(); - }, - function( next ){ this.$el.fadeIn( speed, next ); }, - function( next ){ - this.trigger( 'rendered', view ); - next(); - } - ]); - }, - - /** empty out the current el, move the $newRender's children in */ - _swapNewRender : function( $newRender ){ - return this.$el.empty().attr( 'class', this.className ).append( $newRender.children() ); - }, - - /** 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( $where ){ - $where = $where || this.$el; - // set up canned behavior on children (bootstrap, popupmenus, editable_text, etc.) - //make_popup_menus( $where ); - $where.find( '[title]' ).tooltip({ placement : 'bottom' }); - }, - - // ......................................................................... details - /** shortcut to details DOM (as jQ) */ - $details : function( $where ){ - $where = $where || this.$el; - return $where.find( '.details' ); - }, - - /** build the DOM for the details and set up behaviors on it */ - _renderDetails : function(){ - var $newDetails = $( this.templates.details( this.model.toJSON(), this ) ); - this._setUpBehaviors( $newDetails ); - return $newDetails; - }, - - // ......................................................................... expansion/details - /** Show or hide the details - * @param {Boolean} expand if true, expand; if false, collapse - */ - toggleExpanded : function( expand ){ - expand = ( expand === undefined )?( !this.expanded ):( expand ); - if( expand ){ - this.expand(); - } else { - this.collapse(); - } - return this; - }, - - /** Render and show the full, detailed body of this view including extra data and controls. - * note: if the model does not have detailed data, fetch that data before showing the body - * @fires expanded when a body has been expanded - */ - expand : function(){ - var view = this; - return view._fetchModelDetails() - .always(function(){ - var $newDetails = view._renderDetails(); - view.$details().replaceWith( $newDetails ); - // needs to be set after the above or the slide will not show - view.expanded = true; - $newDetails.slideDown( view.fxSpeed, function(){ - view.trigger( 'expanded', view ); - }); - }); - }, - - /** Check for model details and, if none, fetch them. - * @returns {jQuery.promise} the model.fetch.xhr if details are being fetched, an empty promise if not - */ - _fetchModelDetails : function(){ - if( !this.model.hasDetails() ){ - return this.model.fetch(); - } - return jQuery.when(); - }, - - /** Hide the body/details of an HDA. - * @fires collapsed when a body has been collapsed - */ - collapse : function(){ - var view = this; - view.expanded = false; - this.$details().slideUp( view.fxSpeed, function(){ - view.trigger( 'collapsed', view ); - }); - } - -}); - - -//============================================================================== /** Mixin for views that can be dragged and dropped * Allows for the drag behavior to be turned on/off, setting/removing jQuery event * handlers each time. @@ -711,186 +530,48 @@ //============================================================================== -/** A view that is displayed in some larger list/grid/collection. - * Inherits from Expandable, Selectable, Draggable. - * The DOM contains warnings, a title bar, and a series of primary action controls. - * Primary actions are meant to be easily accessible item functions (such as delete) - * that are rendered in the title bar. +/** Return an underscore template fn from an array of strings. + * @param {String[]} template the template strings to compile into the underscore template fn + * @param {String} jsonNamespace an optional namespace for the json data passed in (defaults to 'model') + * @returns {Function} the (wrapped) underscore template fn + * The function accepts: * - * Details are rendered when the user clicks the title bar or presses enter/space when - * the title bar is in focus. + * The template strings can access: + * the json/model hash using model ("<%- model.myAttr %>) using the jsonNamespace above + * _l: the localizer function + * view (if passed): ostensibly, the view using the template (handy for view instance vars) + * Because they're namespaced, undefined attributes will not throw an error. * - * Designed as a base class for history panel contents - but usable elsewhere (I hope). + * @example: + * templateBler : BASE_MVC.wrapTemplate([ + * '<div class="myclass <%- mynamespace.modelClass %>">', + * '<span><% print( _l( mynamespace.message ) ); %>:<%= view.status %></span>' + * '</div>' + * ], 'mynamespace' ) + * + * Meant to be called in a View's definition in order to compile only once. + * */ -var ListItemView = ExpandableView.extend( mixin( SelectableViewMixin, DraggableViewMixin, { - -//TODO: that's a little contradictory - tagName : 'div', - className : 'list-item', - - /** Set up the base class and all mixins */ - initialize : function( attributes ){ - ExpandableView.prototype.initialize.call( this, attributes ); - SelectableViewMixin.initialize.call( this, attributes ); - DraggableViewMixin.initialize.call( this, attributes ); - }, - - // ........................................................................ rendering - /** In this override, call methods to build warnings, titlebar and primary actions */ - _buildNewRender : function(){ - var $newRender = ExpandableView.prototype._buildNewRender.call( this ); - $newRender.find( '.warnings' ).replaceWith( this._renderWarnings() ); - $newRender.find( '.title-bar' ).replaceWith( this._renderTitleBar() ); - $newRender.find( '.primary-actions' ).append( this._renderPrimaryActions() ); - $newRender.find( '.subtitle' ).replaceWith( this._renderSubtitle() ); - return $newRender; - }, - - /** In this override, render the selector controls and set up dragging before the swap */ - _swapNewRender : function( $newRender ){ - ExpandableView.prototype._swapNewRender.call( this, $newRender ); - if( this.selectable ){ this.showSelector( 0 ); } - if( this.draggable ){ this.draggableOn(); } - return this.$el; - }, - - /** Render any warnings the item may need to show (e.g. "I'm deleted") */ - _renderWarnings : function(){ - var view = this, - $warnings = $( '<div class="warnings"></div>' ), - json = view.model.toJSON(); -//TODO:! unordered (map) - _.each( view.templates.warnings, function( templateFn ){ - $warnings.append( $( templateFn( json, view ) ) ); - }); - return $warnings; - }, - - /** Render the title bar (the main/exposed SUMMARY dom element) */ - _renderTitleBar : function(){ - return $( this.templates.titleBar( this.model.toJSON(), this ) ); - }, - - /** Return an array of jQ objects containing common/easily-accessible item controls */ - _renderPrimaryActions : function(){ - // override this - return []; - }, - - /** Render the title bar (the main/exposed SUMMARY dom element) */ - _renderSubtitle : function(){ - return $( this.templates.subtitle( this.model.toJSON(), this ) ); - }, - - // ......................................................................... events - /** event map */ - events : { - // expand the body when the title is clicked or when in focus and space or enter is pressed - 'click .title-bar' : '_clickTitleBar', - 'keydown .title-bar' : '_keyDownTitleBar', - - // dragging - don't work, originalEvent === null - //'dragstart .dataset-title-bar' : 'dragStartHandler', - //'dragend .dataset-title-bar' : 'dragEndHandler' - - 'click .selector' : 'toggleSelect' - }, - - /** expand when the title bar is clicked */ - _clickTitleBar : function( event ){ - event.stopPropagation(); - this.toggleExpanded(); - }, - - /** expand when the title bar is in focus and enter or space is pressed */ - _keyDownTitleBar : function( event ){ - // 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 ) ){ - this.toggleExpanded(); - event.stopPropagation(); - return false; - } - return true; - }, - - // ......................................................................... misc - /** String representation */ - toString : function(){ - var modelString = ( this.model )?( this.model + '' ):( '(no model)' ); - return 'ListItemView(' + modelString + ')'; - } -})); - -// ............................................................................ TEMPLATES -/** underscore templates */ -ListItemView.prototype.templates = (function(){ -//TODO: move to require text! plugin - - var elTemplato = wrapTemplate([ - '<div class="list-element">', - // errors, messages, etc. - '<div class="warnings"></div>', - - // multi-select checkbox - '<div class="selector">', - '<span class="fa fa-2x fa-square-o"></span>', - '</div>', - // space for title bar buttons - gen. floated to the right - '<div class="primary-actions"></div>', - '<div class="title-bar"></div>', - - // expandable area for more details - '<div class="details"></div>', - '</div>' - ]); - - var warnings = {}; - - var titleBarTemplate = wrapTemplate([ - // adding a tabindex here allows focusing the title bar and the use of keydown to expand the dataset display - '<div class="title-bar clear" tabindex="0">', -//TODO: prob. belongs in dataset-list-item - '<span class="state-icon"></span>', - '<div class="title">', - '<span class="name"><%- element.name %></span>', - '</div>', - '<div class="subtitle"></div>', - '</div>' - ], 'element' ); - - var subtitleTemplate = wrapTemplate([ - // override this - '<div class="subtitle"></div>' - ]); - - var detailsTemplate = wrapTemplate([ - // override this - '<div class="details"></div>' - ]); - - return { - el : elTemplato, - warnings : warnings, - titleBar : titleBarTemplate, - subtitle : subtitleTemplate, - details : detailsTemplate +function wrapTemplate( template, jsonNamespace ){ + jsonNamespace = jsonNamespace || 'model'; + var templateFn = _.template( template.join( '' ) ); + return function( json, view ){ + var templateVars = { view : view || {}, _l : _l }; + templateVars[ jsonNamespace ] = json || {}; + return templateFn( templateVars ); }; -}()); +} //============================================================================== return { LoggableMixin : LoggableMixin, SessionStorageModel : SessionStorageModel, + mixin : mixin, SearchableModelMixin : SearchableModelMixin, HiddenUntilActivatedViewMixin : HiddenUntilActivatedViewMixin, - mixin : mixin, - wrapTemplate : wrapTemplate, - ExpandableView : ExpandableView, DraggableViewMixin : DraggableViewMixin, SelectableViewMixin : SelectableViewMixin, - ListItemView : ListItemView + wrapTemplate : wrapTemplate }; }); diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/collection/collection-li.js --- a/static/scripts/mvc/collection/collection-li.js +++ b/static/scripts/mvc/collection/collection-li.js @@ -1,11 +1,12 @@ define([ + "mvc/list/list-item", "mvc/dataset/dataset-li", "mvc/base-mvc", "utils/localization" -], function( DATASET_LI, BASE_MVC, _l ){ +], function( LIST_ITEM, DATASET_LI, BASE_MVC, _l ){ /* global Backbone, LoggableMixin */ //============================================================================== -var ListItemView = BASE_MVC.ListItemView; +var ListItemView = LIST_ITEM.ListItemView; /** @class Read only view for DatasetCollection. */ var DCListItemView = ListItemView.extend( diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/collection/collection-model.js --- a/static/scripts/mvc/collection/collection-model.js +++ b/static/scripts/mvc/collection/collection-model.js @@ -270,6 +270,11 @@ return this.save( { deleted: false }, options ); }, + /** Is this collection deleted or purged? */ + isDeletedOrPurged : function(){ + return ( this.get( 'deleted' ) || this.get( 'purged' ) ); + }, + // ........................................................................ searchable /** searchable attributes for collections */ searchAttributes : [ diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/collection/collection-panel.js --- a/static/scripts/mvc/collection/collection-panel.js +++ b/static/scripts/mvc/collection/collection-panel.js @@ -1,9 +1,10 @@ define([ + "mvc/list/list-panel", "mvc/collection/collection-model", "mvc/collection/collection-li", "mvc/base-mvc", "utils/localization" -], function( DC_MODEL, DC_LI, BASE_MVC, _l ){ +], function( LIST_PANEL, DC_MODEL, DC_LI, BASE_MVC, _l ){ /* ============================================================================= TODO: @@ -11,224 +12,81 @@ // ============================================================================= /** @class non-editable, read-only View/Controller for a dataset collection. */ -var CollectionPanel = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend( +var _super = LIST_PANEL.ModelListPanel; +var CollectionPanel = _super.extend( /** @lends CollectionPanel.prototype */{ //MODEL is either a DatasetCollection (or subclass) or a DatasetCollectionElement (list of pairs) /** logger used to record this.log messages, commonly set to console */ - //logger : console, + logger : console, - tagName : 'div', - className : 'dataset-collection-panel', - - /** (in ms) that jquery effects will use */ - fxSpeed : 'fast', + className : _super.prototype.className + ' dataset-collection-panel', /** sub view class used for datasets */ DatasetDCEViewClass : DC_LI.DatasetDCEListItemView, /** sub view class used for nested collections */ NestedDCDCEViewClass : DC_LI.NestedDCDCEListItemView, + /** key of attribute in model to assign to this.collection */ + modelCollectionKey : 'elements', // ......................................................................... 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 ); - + _super.prototype.initialize.call( this, attributes ); this.linkTarget = attributes.linkTarget || '_blank'; this.hasUser = attributes.hasUser; this.panelStack = []; this.parentName = attributes.parentName; - //window.collectionPanel = this; - }, - - /** 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 panel - * @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 ); - //this.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 collection data - * @returns {jQuery} dom fragment as temporary container to be swapped out later - */ - renderModel : function( ){ - // tmp div for final swap in render -//TODO: ugh - reuse issue - refactor out - var type = this.model.get( 'collection_type' ) || this.model.object.get( 'collection_type' ), - json = _.extend( this.model.toJSON(), { - parentName : this.parentName, - type : type - }), - $newRender = $( '<div/>' ).append( this.templates.panel( json ) ); - this._setUpBehaviours( $newRender ); - this.renderContents( $newRender ); - return $newRender; - }, - - /** Set up 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 list content views are attached */ - $datasetsList : function( $where ){ - return ( $where || this.$el ).find( '.datasets-list' ); + window.collectionPanel = this; }, // ------------------------------------------------------------------------ sub-views - /** Set up/render a view for each DCE to be shown, init with model and listeners. - * DCE views are cached to the map this.contentViews (using the model.id as key). - * @param {jQuery} $whereTo what dom element to prepend the DCE views to - * @returns the number of visible DCE views - */ - renderContents : function( $whereTo ){ - //this.debug( 'renderContents, elements:', this.model.elements ); - $whereTo = $whereTo || this.$el; - - this.warn( this + '.renderContents:, model:', this.model ); - var panel = this, - contentViews = {}, - //NOTE: no filtering here - visibleContents = this.model.getVisibleContents(); - //this.debug( '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; + /** In this override, use model.getVisibleContents */ + _filterCollection : function(){ +//TODO: should *not* be model.getVisibleContents + return this.model.getVisibleContents(); }, - /** */ - _createContentView : function( content ){ - //this.debug( 'content json:', JSON.stringify( content, null, ' ' ) ); - var contentView = null, - ContentClass = this._getContentClass( content ); - //this.debug( 'content:', content ); - //this.debug( 'ContentClass:', ContentClass ); - contentView = new ContentClass({ - model : content, - linkTarget : this.linkTarget, - //draggable : true, - hasUser : this.hasUser, - logger : this.logger - }); - //this.debug( 'contentView:', contentView ); - this._setUpContentListeners( contentView ); - return contentView; - }, - - /** */ - _getContentClass : function( content ){ - //this.debug( this + '._getContentClass:', content ); + /** override to return proper view class based on element_type */ + _getItemViewClass : function( model ){ + //this.debug( this + '._getItemViewClass:', model ); //TODO: subclasses use DCEViewClass - but are currently unused - decide - switch( content.get( 'element_type' ) ){ + switch( model.get( 'element_type' ) ){ case 'hda': return this.DatasetDCEViewClass; case 'dataset_collection': return this.NestedDCDCEViewClass; } - throw new TypeError( 'Unknown element type:', content.get( 'element_type' ) ); + throw new TypeError( 'Unknown element type:', model.get( 'element_type' ) ); }, - /** Set up listeners for content view events. In this override, handle collection expansion. */ - _setUpContentListeners : function( contentView ){ + /** override to add link target and anon */ + _getItemViewOptions : function( model ){ + var options = _super.prototype._getItemViewOptions.call( this, model ); + return _.extend( options, { + linkTarget : this.linkTarget, + hasUser : this.hasUser + }); + }, + + /** when a sub-view is clicked in the collection panel that is itself a collection, + * hide this panel's elements and show the sub-collection in its own panel. + */ + _setUpItemViewListeners : function( view ){ var panel = this; - if( contentView.model.get( 'element_type' ) === 'dataset_collection' ){ - contentView.on( 'expanded', function( collectionView ){ + _super.prototype._setUpItemViewListeners.call( panel, view ); + //TODO:?? doesn't seem to belong here + if( view.model.get( 'element_type' ) === 'dataset_collection' ){ + view.on( 'expanded', function( collectionView ){ panel.info( 'expanded', collectionView ); panel._addCollectionPanel( collectionView ); }); } + return panel; }, /** When a sub-collection is clicked, hide the current panel and render the sub-collection in its own panel */ @@ -246,7 +104,7 @@ }); currPanel.panelStack.push( panel ); - currPanel.$( '.controls' ).add( '.datasets-list' ).hide(); + currPanel.$( '.controls' ).add( '.list-items' ).hide(); currPanel.$el.append( panel.$el ); panel.on( 'close', function(){ currPanel.render(); @@ -266,14 +124,6 @@ } }, - /** 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 : { @@ -293,16 +143,16 @@ } }); -//----------------------------------------------------------------------------- TEMPLATES -/** underscore templates */ -CollectionPanel.templates = CollectionPanel.prototype.templates = (function(){ -// use closure to run underscore template fn only once at module load - var _panelTemplate = _.template([ + +//------------------------------------------------------------------------------ TEMPLATES +CollectionPanel.prototype.templates = (function(){ + + var controlsTemplate = BASE_MVC.wrapTemplate([ '<div class="controls">', '<div class="navigation">', '<a class="back" href="javascript:void(0)">', '<span class="fa fa-icon fa-angle-left"></span>', - _l( 'Back to ' ), '<%- collection.parentName %>', + _l( 'Back to ' ), '<%- view.parentName %>', '</a>', '</div>', @@ -310,29 +160,25 @@ '<div class="name"><%- collection.name || collection.element_identifier %></div>', '<div class="subtitle">', //TODO: remove logic from template - '<% if( collection.type === "list" ){ %>', + '<% if( collection.collection_type === "list" ){ %>', _l( 'a list of datasets' ), - '<% } else if( collection.type === "paired" ){ %>', + '<% } else if( collection.collection_type === "paired" ){ %>', _l( 'a pair of datasets' ), - '<% } else if( collection.type === "list:paired" ){ %>', + '<% } else if( collection.collection_type === "list:paired" ){ %>', _l( 'a list of paired datasets' ), '<% } %>', '</div>', '</div>', - '</div>', - // where the datasets/hdas are added - '<div class="datasets-list"></div>' - ].join( '' )); + '</div>' + ], 'collection' ); - // we override here in order to pass the localizer (_L) into the template scope - since we use it as a fn within - return { - panel : function( json ){ - return _panelTemplate({ _l: _l, collection: json }); - } - }; + return _.extend( _.clone( _super.prototype.templates ), { + controls : controlsTemplate + }); }()); + // ============================================================================= /** @class non-editable, read-only View/Controller for a dataset collection. */ var ListCollectionPanel = CollectionPanel.extend( diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/dataset/dataset-li.js --- a/static/scripts/mvc/dataset/dataset-li.js +++ b/static/scripts/mvc/dataset/dataset-li.js @@ -1,8 +1,9 @@ define([ + "mvc/list/list-item", "mvc/dataset/states", "mvc/base-mvc", "utils/localization" -], function( STATES, BASE_MVC, _l ){ +], function( LIST_ITEM, STATES, BASE_MVC, _l ){ /* global Backbone */ /*============================================================================== TODO: @@ -11,7 +12,7 @@ simplify button rendering ==============================================================================*/ -var _super = BASE_MVC.ListItemView; +var _super = LIST_ITEM.ListItemView; /** @class Read only list view for either LDDAs, HDAs, or HDADCEs. * Roughly, any DatasetInstance (and not a raw Dataset). */ diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/dataset/dataset-list.js --- a/static/scripts/mvc/dataset/dataset-list.js +++ b/static/scripts/mvc/dataset/dataset-list.js @@ -1,5 +1,5 @@ define([ - "mvc/dataset/list-panel", + "mvc/list/list-panel", "mvc/dataset/dataset-li", "mvc/base-mvc", "utils/localization" diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/dataset/dataset-model.js --- a/static/scripts/mvc/dataset/dataset-model.js +++ b/static/scripts/mvc/dataset/dataset-model.js @@ -94,6 +94,9 @@ this.trigger( 'state:ready', currModel, newState, this.previous( 'state' ) ); } }); + this.on( 'change:urls', function(){ + console.warn( 'change:urls', arguments ); + }); // the download url (currenlty) relies on having a correct file extension this.on( 'change:id change:file_ext', function( currModel ){ this._generateUrls(); @@ -104,6 +107,8 @@ /** override to add urls */ toJSON : function(){ var json = Backbone.Model.prototype.toJSON.call( this ); + //console.warn( 'returning json?' ); + //return json; return _.extend( json, { urls : this.urls }); diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/dataset/list-panel.js --- a/static/scripts/mvc/dataset/list-panel.js +++ /dev/null @@ -1,687 +0,0 @@ -define([ - "mvc/base-mvc", - "utils/localization" -], function( BASE_MVC, _l ){ -/* ============================================================================= -TODO: - -============================================================================= */ -/** @class List that contains ListItemViews. - */ -var ListPanel = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend( -/** @lends ReadOnlyHistoryPanel.prototype */{ - - /** logger used to record this.log messages, commonly set to console */ - //logger : console, - - /** class to use for constructing the sub-views */ - viewClass : BASE_MVC.ListItemView, - - tagName : 'div', - className : 'list-panel', - - /** (in ms) that jquery effects will use */ - fxSpeed : 'fast', - - /** string to display when the model has no hdas */ - emptyMsg : _l( 'This list is empty' ), - /** string to no hdas match the search terms */ - noneFoundMsg : _l( 'No matching items found' ), - - // ......................................................................... SET UP - /** Set up the view, set up storage, bind listeners to HistoryContents events - * @param {Object} attributes optional settings for the list - */ - initialize : function( attributes, options ){ - attributes = attributes || {}; - // set the logger if requested - if( attributes.logger ){ - this.logger = attributes.logger; - } - this.log( this + '.initialize:', attributes ); - - // ---- instance vars - /** how quickly should jquery fx run? */ - this.fxSpeed = _.has( attributes, 'fxSpeed' )?( attributes.fxSpeed ):( this.fxSpeed ); - - /** filters for displaying subviews */ - this.filters = []; - /** current search terms */ - this.searchFor = attributes.searchFor || ''; - - /** loading indicator */ - this.indicator = new LoadingIndicator( this.$el ); - - /** currently showing selectors on items? */ - this.selecting = ( attributes.selecting !== undefined )? attributes.selecting : true; - //this.selecting = false; - - /** cached selected item.model.ids to persist btwn renders */ - this.selected = attributes.selected || []; - /** the last selected item.model.id */ - this.lastSelected = null; - - /** list item view class (when passed models) */ - this.viewClass = attributes.viewClass || this.viewClass; - - /** list item views */ - this.views = []; - /** list item models */ - this.collection = attributes.collection || ( new Backbone.Collection([]) ); - - /** filter fns run over collection items to see if they should show in the list */ - this.filters = attributes.filters || []; - -//TODO: remove - this.title = attributes.title || ''; - this.subtitle = attributes.subtitle || ''; - - this._setUpListeners(); - }, - - /** create any event listeners for the list - */ - _setUpListeners : function(){ - this.on( 'error', function( model, xhr, options, msg, details ){ - //this.errorHandler( model, xhr, options, msg, details ); - console.error( model, xhr, options, msg, details ); - }, this ); - - // show hide the loading indicator - this.on( 'loading', function(){ - this._showLoadingIndicator( 'loading...', 40 ); - }, this ); - this.on( 'loading-done', function(){ - this._hideLoadingIndicator( 40 ); - }, this ); - - // throw the first render up as a diff namespace using once (for outside consumption) - this.once( 'rendered', function(){ - this.trigger( 'rendered:initial', this ); - }, this ); - - // debugging - if( this.logger ){ - this.on( 'all', function( event ){ - this.log( this + '', arguments ); - }, this ); - } - - this._setUpCollectionListeners(); - this._setUpViewListeners(); - return this; - }, - - /** free any sub-views the list has */ - freeViews : function(){ -//TODO: stopListening? remove? - this.views = []; - return this; - }, - - // ------------------------------------------------------------------------ item listeners - /** listening for history and HDA events */ - _setUpCollectionListeners : function(){ - - this.collection.on( 'reset', function(){ - this.renderItems(); - }, this ); - - this.collection.on( 'add', this.addItemView, this ); - this.collection.on( 'remove', this.removeItemView, this ); - - // debugging - if( this.logger ){ - this.collection.on( 'all', function( event ){ - this.info( this + '(collection)', arguments ); - }, this ); - } - return this; - }, - - /** listening for history and HDA events */ - _setUpViewListeners : function(){ - - // shift to select a range - this.on( 'view:selected', function( view, ev ){ - if( ev && ev.shiftKey && this.lastSelected ){ - var lastSelectedView = _.find( this.views, function( view ){ - return view.model.id === this.lastSelected; - }); - if( lastSelectedView ){ - this.selectRange( view, lastSelectedView ); - } - } - this.selected.push( view.model.id ); - this.lastSelected = view.model.id; - }, this ); - }, - - // ------------------------------------------------------------------------ rendering - /** Render this content, set up ui. - * @param {Number or String} speed the speed of the render - */ - render : function( speed ){ - var $newRender = this._buildNewRender(); - this._setUpBehaviors( $newRender ); - this._queueNewRender( $newRender, speed ); - return this; - }, - - /** Build a temp div containing the new children for the view's $el. - */ - _buildNewRender : function(){ - // create a new render using a skeleton template, render title buttons, render body, and set up events, etc. - var json = this.model? this.model.toJSON() : {}, - $newRender = $( this.templates.el( json, this ) ); - this._renderTitle( $newRender ); - this._renderSubtitle( $newRender ); - this._renderSearch( $newRender ); - this.renderItems( $newRender ); - return $newRender; - }, - - /** - */ - _renderTitle : function( $where ){ - //$where = $where || this.$el; - //$where.find( '.title' ).replaceWith( ... ) - }, - - /** - */ - _renderSubtitle : function( $where ){ - //$where = $where || this.$el; - //$where.find( '.title' ).replaceWith( ... ) - }, - - /** Fade out the old el, swap in the new contents, then fade in. - * @param {Number or String} speed jq speed to use for rendering effects - * @fires rendered when rendered - */ - _queueNewRender : function( $newRender, speed ) { - speed = ( speed === undefined )?( this.fxSpeed ):( speed ); - var view = this; - - $( view ).queue( 'fx', [ - function( next ){ this.$el.fadeOut( speed, next ); }, - function( next ){ - view._swapNewRender( $newRender ); - next(); - }, - function( next ){ this.$el.fadeIn( speed, next ); }, - function( next ){ - view.trigger( 'rendered', view ); - next(); - } - ]); - }, - - /** empty out the current el, move the $newRender's children in */ - _swapNewRender : function( $newRender ){ - this.$el.empty().attr( 'class', this.className ).append( $newRender.children() ); - if( this.selecting ){ this.showSelectors( 0 ); } - return this; - }, - - /** */ - _setUpBehaviors : function( $where ){ - $where = $where || this.$el; - $where.find( '.controls [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 */ - $scrollContainer : function(){ - // override - return this.$el.parent().parent(); - }, - /** */ - $list : function( $where ){ - return ( $where || this.$el ).find( '.list-items' ); - }, - /** container where list messages are attached */ - $messages : function( $where ){ - return ( $where || this.$el ).find( '.message-container' ); - }, - /** the message displayed when no views can be shown (no views, none matching search) */ - $emptyMessage : function( $where ){ - return ( $where || this.$el ).find( '.empty-message' ); - }, - - // ------------------------------------------------------------------------ hda sub-views - /** - * @param {jQuery} $whereTo what dom element to prepend the HDA views to - * @returns the visible item views - */ - renderItems : function( $whereTo ){ - $whereTo = $whereTo || this.$el; - var list = this, - newViews = []; - - var $list = this.$list( $whereTo ), - item$els = this._filterCollection().map( function( itemModel ){ -//TODO: creates views each time - not neccessarily good - var view = list._createItemView( itemModel ); - newViews.push( view ); - return view.render( 0 ).$el; - }); - this.debug( item$els ); - this.debug( newViews ); - - $list.empty(); - if( item$els.length ){ - $list.append( item$els ); - this.$emptyMessage( $whereTo ).hide(); - - } else { - this._renderEmptyMessage( $whereTo ).show(); - } - - this.views = newViews; - return newViews; - }, - - /** - */ - _filterCollection : function(){ - // override this - var list = this; - return list.collection.filter( _.bind( list._filterItem, list ) ); - }, - - /** - */ - _filterItem : function( model ){ - // override this - var list = this; - return ( _.every( list.filters.map( function( fn ){ return fn.call( model ); }) ) ) - && ( !list.searchFor || model.matchesAll( list.searchFor ) ); - }, - - /** - */ - _createItemView : function( model ){ - var ViewClass = this._getItemViewClass( model ), - options = _.extend( this._getItemViewOptions( model ), { - model : model - }), - view = new ViewClass( options ); - this._setUpItemViewListeners( view ); - return view; - }, - - _getItemViewClass : function( model ){ - // override this - return this.viewClass; - }, - - _getItemViewOptions : function( model ){ - // override this - return { - //logger : this.logger, - fxSpeed : this.fxSpeed, - expanded : false, - selectable : this.selecting, - selected : _.contains( this.selected, model.id ), - draggable : this.dragging - }; - }, - - /** - */ - _setUpItemViewListeners : function( view ){ - var list = this; - view.on( 'all', function(){ - var args = Array.prototype.slice.call( arguments, 0 ); - args[0] = 'view:' + args[0]; - list.trigger.apply( list, args ); - }); - - // debugging - //if( this.logger ){ - // view.on( 'all', function( event ){ - // this.log( this + '(view)', arguments ); - // }, this ); - //} - return this; - }, - - /** render the empty/none-found message */ - _renderEmptyMessage : function( $whereTo ){ - //this.debug( '_renderEmptyMessage', $whereTo, this.searchFor ); - var text = this.searchFor? this.noneFoundMsg : this.emptyMsg; - return this.$emptyMessage( $whereTo ).text( text ); - }, - - /** collapse all item views */ - expandAll : function(){ - _.each( this.views, function( view ){ - view.expand(); - }); - }, - - /** collapse all item views */ - collapseAll : function(){ - _.each( this.views, function( view ){ - view.collapse(); - }); - }, - - // ------------------------------------------------------------------------ collection/views syncing - /** - */ - addItemView : function( model, collection, options ){ - this.log( this + '.addItemView:', model ); - var list = this; - if( !this._filterItem( model ) ){ return undefined; } - -//TODO: sorted? position? - var view = list._createItemView( model ); - this.views.push( view ); - - $( view ).queue( 'fx', [ - function( next ){ list.$emptyMessage().fadeOut( list.fxSpeed, next ); }, - function( next ){ -//TODO: auto render? - list.$list().append( view.render().$el ); - next(); - } - ]); - return view; - }, - - /** - */ - removeItemView : function( model, collection, options ){ - this.log( this + '.removeItemView:', model ); - var list = this, - view = list.viewFromModel( model ); - if( !view ){ return undefined; } - - this.views = _.without( this.views, view ); - view.remove(); - if( !this.views.length ){ - list._renderEmptyMessage().fadeIn( list.fxSpeed ); - } - return view; - }, - - /** get views based on model - */ - viewFromModel : function( model ){ - for( var i=0; i<this.views.length; i++ ){ - var view = this.views[i]; - if( view.model === model ){ - return view; - } - } - return undefined; - }, - - /** get views based on model properties - */ - viewsWhereModel : function( properties ){ - return this.views.filter( function( view ){ - //return view.model.matches( properties ); -//TODO: replace with _.matches (underscore 1.6.0) - var json = view.model.toJSON(); - //console.debug( '\t', json, properties ); - for( var key in properties ){ - if( properties.hasOwnPropery( key ) ){ - //console.debug( '\t\t', json[ key ], view.model.properties[ key ] ); - if( json[ key ] !== view.model.properties[ key ] ){ - return false; - } - } - } - return true; - }); - }, - - /** - */ - viewRange : function( viewA, viewB ){ - if( viewA === viewB ){ return ( viewA )?( [ viewA ] ):( [] ); } - - var indexA = this.views.indexOf( viewA ), - indexB = this.views.indexOf( viewB ); - - // handle not found - if( indexA === -1 || indexB === -1 ){ - if( indexA === indexB ){ return []; } - return ( indexA === -1 )?( [ viewB ] ):( [ viewA ] ); - } - // reverse if indeces are - //note: end inclusive - return ( indexA < indexB )? - this.views.slice( indexA, indexB + 1 ) : - this.views.slice( indexB, indexA + 1 ); - }, - - // ------------------------------------------------------------------------ searching - /** render a search input for filtering datasets shown - * (see the search section in the HDA model for implementation of the actual searching) - * return will start the search - * esc will clear the search - * clicking the clear button will clear the search - * uses searchInput in ui.js - */ - _renderSearch : function( $where ){ - $where.find( '.controls .search-input' ).searchInput({ - placeholder : 'search', - initialVal : this.searchFor, - onfirstsearch : _.bind( this._firstSearch, this ), - onsearch : _.bind( this.searchItems, this ), - onclear : _.bind( this.clearSearch, this ) - }); - return $where; - }, - - _firstSearch : function( searchFor ){ - this.log( 'onFirstSearch', searchFor ); - return this.searchItems( searchFor ); - }, - - /** filter view list to those that contain the searchFor terms */ - searchItems : function( searchFor ){ - this.searchFor = searchFor; - this.trigger( 'search:searching', searchFor, this ); - this.renderItems(); - return this; - }, - - /** clear the search filters and show all views that are normally shown */ - clearSearch : function( searchFor ){ - //this.log( 'onSearchClear', this ); - this.searchFor = ''; - this.trigger( 'search:clear', this ); - this.renderItems(); - return this; - }, - - // ------------------------------------------------------------------------ selection - /** show selectors on all visible hdas and associated controls */ - showSelectors : function( speed ){ - speed = ( speed !== undefined )?( speed ):( this.fxSpeed ); - this.selecting = true; - this.$( '.list-actions' ).slideDown( speed ); - _.each( this.views, function( view ){ - view.showSelector( speed ); - }); - this.selected = []; - this.lastSelected = null; - }, - - /** hide selectors on all visible hdas and associated controls */ - hideSelectors : function( speed ){ - speed = ( speed !== undefined )?( speed ):( this.fxSpeed ); - this.selecting = false; - this.$( '.list-actions' ).slideUp( speed ); - _.each( this.views, function( view ){ - view.hideSelector( speed ); - }); - this.selected = []; - this.lastSelected = null; - }, - - /** show or hide selectors on all visible hdas and associated controls */ - toggleSelectors : function(){ - if( !this.selecting ){ - this.showSelectors(); - } else { - this.hideSelectors(); - } - }, - - /** select all visible hdas */ - selectAll : function( event ){ - _.each( this.views, function( view ){ - view.select( event ); - }); - }, - - /** deselect all visible hdas */ - deselectAll : function( event ){ - this.lastSelected = null; - _.each( this.views, function( view ){ - view.deselect( event ); - }); - }, - - /** select a range of datasets between A and B */ - selectRange : function( viewA, viewB ){ - var range = this.viewRange( viewA, viewB ); - _.each( range, function( view ){ - view.select(); - }); - return range; - }, - - /** return an array of all currently selected hdas */ - getSelectedViews : function(){ - return _.filter( this.views, function( v ){ - return v.selected; - }); - }, - - /** return an collection of the models of all currenly selected hdas */ - getSelectedModels : function(){ - return new this.collection.constructor( _.map( this.getSelectedViews(), function( view ){ - return view.model; - })); - }, - - // ------------------------------------------------------------------------ loading indicator -//TODO: questionable - /** hide the $el and display a loading indicator (in the $el's parent) when loading new data */ - _showLoadingIndicator : function( msg, speed, callback ){ - speed = ( speed !== undefined )?( speed ):( this.fxSpeed ); - if( !this.indicator ){ - this.indicator = new LoadingIndicator( this.$el, this.$el.parent() ); - } - if( !this.$el.is( ':visible' ) ){ - this.indicator.show( 0, callback ); - } else { - this.$el.fadeOut( speed ); - this.indicator.show( msg, speed, callback ); - } - }, - - /** hide the loading indicator */ - _hideLoadingIndicator : function( speed, callback ){ - speed = ( speed !== undefined )?( speed ):( this.fxSpeed ); - if( this.indicator ){ - this.indicator.hide( speed, callback ); - } - }, - - // ------------------------------------------------------------------------ scrolling - /** get the current scroll position of the panel in its parent */ - scrollPosition : function(){ - return this.$scrollContainer().scrollTop(); - }, - - /** set the current scroll position of the panel in its parent */ - scrollTo : function( pos ){ - this.$scrollContainer().scrollTop( pos ); - return this; - }, - - /** Scrolls the panel to the top. */ - scrollToTop : function(){ - this.$scrollContainer().scrollTop( 0 ); - return this; - }, - - /** */ - scrollToItem : function( view ){ - if( !view ){ return; } - var itemTop = view.$el.offset().top; - this.$scrollContainer().scrollTop( itemTop ); - }, - - // ------------------------------------------------------------------------ panel events - /** event map */ - events : { - 'click .select-all' : 'selectAll', - 'click .deselect-all' : 'deselectAll' - }, - - // ------------------------------------------------------------------------ misc - /** Return a string rep of the history */ - toString : function(){ - return 'ListPanel(' + this.collection + ')'; - } -}); - -// ............................................................................ TEMPLATES -/** underscore templates */ -ListPanel.prototype.templates = (function(){ -//TODO: move to require text! plugin - - var elTemplate = BASE_MVC.wrapTemplate([ - // temp container - '<div>', - '<div class="controls">', - '<div class="title">', - '<div class="name"><%= model.name || view.title %></div>', - '</div>', - '<div class="subtitle"><%= view.subtitle %></div>', - '<div class="actions"></div>', - '<div class="messages"></div>', - - '<div class="search">', - '<div class="search-input"></div>', - '</div>', - - '<div class="list-actions">', - '<div class="btn-group">', - '<button class="select-all btn btn-default"', - 'data-mode="select">', _l( 'All' ), '</button>', - '<button class="deselect-all btn btn-default"', - 'data-mode="select">', _l( 'None' ), '</button>', - '</div>', - //'<button class="action-popup-btn btn btn-default">', - // _l( 'For all selected' ), '...', - //'</button>', - '</div>', - '</div>', - '<div class="list-items"></div>', - '<div class="empty-message infomessagesmall"></div>', - '</div>' - ]); - - return { - el : elTemplate - }; -}()); - - - -//============================================================================== - return { - ListPanel: ListPanel - }; -}); diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/history/hda-li.js --- a/static/scripts/mvc/history/hda-li.js +++ b/static/scripts/mvc/history/hda-li.js @@ -17,12 +17,12 @@ /** logger used to record this.log messages, commonly set to console */ //logger : console, + className : _super.prototype.className + " history-content", + initialize : function( attributes, options ){ _super.prototype.initialize.call( this, attributes, options ); }, - className : _super.prototype.className + " history-content", - // ......................................................................... misc /** String representation */ toString : function(){ diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/history/history-contents.js --- a/static/scripts/mvc/history/history-contents.js +++ b/static/scripts/mvc/history/history-contents.js @@ -53,6 +53,7 @@ */ initialize : function( models, options ){ options = options || {}; +//TODO: could probably use the contents.history_id instead this.historyId = options.historyId; //this._setUpListeners(); @@ -226,7 +227,7 @@ if( !existing ){ return model; } // merge the models _BEFORE_ calling the superclass version - var merged = existing.toJSON(); + var merged = _.clone( existing.attributes ); _.extend( merged, model ); return merged; }); @@ -303,6 +304,13 @@ return xhr; }, + /** In this override, copy the historyId to the clone */ + clone : function(){ + var clone = Backbone.Collection.prototype.clone.call( this ); + clone.historyId = this.historyId; + return clone; + }, + /** debugging */ print : function(){ var contents = this; diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/history/history-model.js --- a/static/scripts/mvc/history/history-model.js +++ b/static/scripts/mvc/history/history-model.js @@ -232,7 +232,7 @@ /** Get data for a history then its hdas using a sequential ajax call, return a deferred to receive both */ History.getHistoryData = function getHistoryData( historyId, options ){ options = options || {}; - var hdaDetailIds = options.hdaDetailIds || []; + var detailIdsFn = options.detailIdsFn || []; var hdcaDetailIds = options.hdcaDetailIds || []; //console.debug( 'getHistoryData:', historyId, options ); @@ -250,20 +250,20 @@ // get the number of hdas accrd. to the history return historyData && historyData.empty; } - function getHdas( historyData ){ + function getContents( historyData ){ // get the hda data // if no hdas accrd. to history: return empty immed. if( isEmpty( historyData ) ){ return []; } // if there are hdas accrd. to history: get those as well - if( _.isFunction( hdaDetailIds ) ){ - hdaDetailIds = hdaDetailIds( historyData ); + if( _.isFunction( detailIdsFn ) ){ + detailIdsFn = detailIdsFn( historyData ); } if( _.isFunction( hdcaDetailIds ) ){ hdcaDetailIds = hdcaDetailIds( historyData ); } var data = {}; - if( hdaDetailIds.length ) { - data.dataset_details = hdaDetailIds.join( ',' ); + if( detailIdsFn.length ) { + data.dataset_details = detailIdsFn.join( ',' ); } if( hdcaDetailIds.length ) { // for symmetry, not actually used by backend of consumed @@ -274,10 +274,10 @@ } // getting these concurrently is 400% slower (sqlite, local, vanilla) - so: - // chain the api calls - getting history first then hdas + // chain the api calls - getting history first then contents var historyFn = options.historyFn || getHistory, - hdaFn = options.hdaFn || getHdas; + contentsFn = options.contentsFn || getContents; // chain ajax calls: get history first, then hdas var historyXHR = historyFn( historyId ); @@ -291,15 +291,15 @@ df.reject( xhr, 'loading the history' ); }); - var hdaXHR = historyXHR.then( hdaFn ); - hdaXHR.then( function( hdaJSON ){ - df.notify({ status: 'dataset data retrieved', historyJSON: historyJSON, hdaJSON: hdaJSON }); + var contentsXHR = historyXHR.then( contentsFn ); + contentsXHR.then( function( contentsJSON ){ + df.notify({ status: 'contents data retrieved', historyJSON: historyJSON, contentsJSON: contentsJSON }); // we've got both: resolve the outer scope deferred - df.resolve( historyJSON, hdaJSON ); + df.resolve( historyJSON, contentsJSON ); }); - hdaXHR.fail( function( xhr, status, message ){ + contentsXHR.fail( function( xhr, status, message ){ // call reject on the outer deferred to allow its fail callback to run - df.reject( xhr, 'loading the datasets', { history: historyJSON } ); + df.reject( xhr, 'loading the contents', { history: historyJSON } ); }); return df; diff -r 9d15e899516411c9b491f66f001a55136b1173b9 -r b0c8da2b3c1e37b3108bd6bc6b8ad919afef9b43 static/scripts/mvc/history/history-panel-annotated.js --- a/static/scripts/mvc/history/history-panel-annotated.js +++ b/static/scripts/mvc/history/history-panel-annotated.js @@ -1,118 +1,97 @@ define([ "mvc/history/history-panel", "mvc/history/hda-li", + "mvc/history/hdca-li", + "mvc/base-mvc", "utils/localization" -], function( HPANEL, HDA_LI, _l ){ +], function( HPANEL, HDA_LI, HDCA_LI, BASE_MVC, _l ){ /* ============================================================================= TODO: ============================================================================= */ -var _super = HPANEL.ReadOnlyHistoryPanel; +var _super = HPANEL.HistoryPanel; // used in history/display.mako and history/embed.mako /** @class View/Controller for a tabular view of the history model. - * @name AnnotatedHistoryPanel * * As ReadOnlyHistoryPanel, but with: * history annotation always shown * datasets displayed in a table: * datasets in left cells, dataset annotations in the right - * - * @augments Backbone.View - * @borrows LoggableMixin#logger as #logger - * @borrows LoggableMixin#log as #log - * @constructs */ var AnnotatedHistoryPanel = _super.extend( /** @lends AnnotatedHistoryPanel.prototype */{ /** logger used to record this.log messages, commonly set to console */ - // comment this out to suppress log output //logger : console, - className : 'annotated-history-panel', - - //TODO:?? possibly into own annotated class - /** class to use for constructing the HDA views */ - //HDAViewClass : HDA_LI.HDAListItemView, + className : _super.prototype.className + ' annotated-history-panel', // ------------------------------------------------------------------------ panel rendering - /** render with history data - * In this override: - * replace the datasets list with a table, - * add the history annotation, - * and move the search controls - * @returns {jQuery} dom fragment as temporary container to be swapped out later + /** In this override, add the history annotation */ - renderModel : function( ){ - // why do we need this here? why isn't className being applied? - this.$el.addClass( this.className ); - var $newRender = _super.prototype.renderModel.call( this ), - // move datasets from div to table - $datasetsList = this.$datasetsList( $newRender ), - $datasetsTable = $( '<table/>' ).addClass( 'datasets-list datasets-table' ); - $datasetsTable.append( $datasetsList.children() ); - $datasetsList.replaceWith( $datasetsTable ); - //TODO: it's possible to do this with css only, right? display: table-cell, etc.? - - // add history annotation under subtitle - $newRender.find( '.history-subtitle' ).after( this.renderHistoryAnnotation() ); - - // hide search button, move search bar beneath controls (instead of above title), show, and set up - $newRender.find( '.history-search-btn' ).hide(); - $newRender.find( '.history-controls' ).after( $newRender.find( '.history-search-controls' ).show() ); - + _buildNewRender : function(){ + //TODO: shouldn't this display regardless (on all non-current panels)? + var $newRender = _super.prototype._buildNewRender.call( this ); + this.renderHistoryAnnotation( $newRender ); return $newRender; }, /** render the history's annotation as its own field */ - renderHistoryAnnotation : function(){ + renderHistoryAnnotation : function( $newRender ){ var annotation = this.model.get( 'annotation' ); - if( !annotation ){ return null; } - return $([ - '<div class="history-annotation">', annotation, '</div>' - ].join( '' )); + if( !annotation ){ return; } + $newRender.find( '.controls .annotation-display' ).text( annotation ); }, - /** Set up/render a view for each HDA to be shown, init with model and listeners. - * In this override, add table header cells to indicate the dataset, annotation columns + /** In this override, convert the list-items tag to a table + * and add table header cells to indicate the dataset, annotation columns */ - renderHdas : function( $whereTo ){ + renderItems : function( $whereTo ){ $whereTo = $whereTo || this.$el; - var hdaViews = _super.prototype.renderHdas.call( this, $whereTo ); - this.$datasetsList( $whereTo ).prepend( $( '<tr/>' ).addClass( 'headers' ).append([ - $( '<th/>' ).text( _l( 'Dataset' ) ), - $( '<th/>' ).text( _l( 'Annotation' ) ) - ])); - return hdaViews; + + // convert to table + $whereTo.find( '.list-items' ) + .replaceWith( $( '<table/>' ).addClass( 'list-items' ) ); + + // render rows/contents and prepend headers + var views = _super.prototype.renderItems.call( this, $whereTo ); + this.$list( $whereTo ) + .prepend( $( '<tr/>' ).addClass( 'headers' ).append([ + $( '<th/>' ).text( _l( 'Dataset' ) ), + $( '<th/>' ).text( _l( 'Annotation' ) ) + ])); + return views; }, - // ------------------------------------------------------------------------ hda sub-views - /** attach an hdaView to the panel - * In this override, wrap the hdaView in a table row and cell, adding a 2nd cell for the hda annotation + // ------------------------------------------------------------------------ sub-views + /** In this override, wrap the content view in a table row + * with the content in the left td and annotation/extra-info in the right */ - attachContentView : function( hdaView, $whereTo ){ - $whereTo = $whereTo || this.$el; - // build a row around the dataset with the std hdaView in the first cell and the annotation in the next - var stateClass = _.find( hdaView.el.classList, function( c ){ return ( /^state\-/ ).test( c ); }), - annotation = hdaView.model.get( 'annotation' ) || '', - $tr = $( '<tr/>' ).addClass( 'dataset-row' ).append([ - $( '<td/>' ).addClass( 'dataset-container' ).append( hdaView.$el ) - // visually match the cell bg to the dataset at runtime (prevents the empty space) - // (getting bg via jq on hidden elem doesn't work on chrome/webkit - so use states) - //.css( 'background-color', hdaView.$el.css( 'background-color' ) ), - .addClass( stateClass? stateClass.replace( '-', '-color-' ): '' ), - $( '<td/>' ).addClass( 'additional-info' ).text( annotation ) - ]); - this.$datasetsList( $whereTo ).append( $tr ); + _attachItems : function( $whereTo ){ + this.$list( $whereTo ).append( this.views.map( function( view ){ + //TODO:?? possibly make this more flexible: instead of annotation use this._additionalInfo() + // build a row around the dataset with the std itemView in the first cell and the annotation in the next + var stateClass = _.find( view.el.classList, function( c ){ return ( /^state\-/ ).test( c ); }), + annotation = view.model.get( 'annotation' ) || '', + $tr = $( '<tr/>' ).append([ + $( '<td/>' ).addClass( 'contents-container' ).append( view.$el ) + // visually match the cell bg to the dataset at runtime (prevents the empty space) + // (getting bg via jq on hidden elem doesn't work on chrome/webkit - so use states) + //.css( 'background-color', view.$el.css( 'background-color' ) ), + .addClass( stateClass? stateClass.replace( '-', '-color-' ): '' ), + $( '<td/>' ).addClass( 'additional-info' ).text( annotation ) + ]); + return $tr; + })); + return this; }, // ------------------------------------------------------------------------ panel events /** event map */ events : _.extend( _.clone( _super.prototype.events ), { + // clicking on any part of the row will expand the items 'click tr' : function( ev ){ - //if( !ev.target.hasAttribute( 'href' ) ){ - $( ev.currentTarget ).find( '.dataset-title-bar' ).click(); - //} + $( ev.currentTarget ).find( '.title-bar' ).click(); }, // prevent propagation on icon btns so they won't bubble up to tr and toggleBodyVisibility 'click .icon-btn' : function( ev ){ @@ -127,6 +106,7 @@ } }); + //============================================================================== return { AnnotatedHistoryPanel : AnnotatedHistoryPanel 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.
participants (1)
-
commits-noreply@bitbucket.org