1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/700560884da7/
Changeset: 700560884da7
User: carlfeberhard
Date: 2014-08-11 17:07:34
Summary: HDA/Collections client-side refactoring for drill down view
Affected #: 48 files
diff -r bdc4017c2e7e9ecb5dfa3d36798f535402ec80aa -r 700560884da71e8bd3bdad6ddbe4edfde9566f7f static/scripts/mvc/base-mvc.js
--- a/static/scripts/mvc/base-mvc.js
+++ b/static/scripts/mvc/base-mvc.js
@@ -53,6 +53,7 @@
//==============================================================================
/** Backbone model that syncs to the browser's sessionStorage API.
+ * This all largely happens behind the scenes and no special calls are required.
*/
var SessionStorageModel = Backbone.Model.extend({
initialize : function( initialAttrs ){
@@ -147,13 +148,128 @@
//==============================================================================
+/** A mixin for models that allow T/F/Matching to their attributes - useful when
+ * searching or filtering collections of models.
+ * @example:
+ * see hda-model for searchAttribute and searchAliases definition examples.
+ * see history-contents.matches for how collections are filtered
+ * and see readonly-history-panel.searchHdas for how user input is connected to the filtering
+ */
+var SearchableModelMixin = {
+
+ /** what attributes of an HDA will be used in a text search */
+ searchAttributes : [
+ // override
+ ],
+
+ /** 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 : {
+ // override
+ },
+
+ /** search the attribute with key attrKey for the string searchFor; T/F if found */
+ searchAttribute : function( attrKey, searchFor ){
+ var attrVal = this.get( attrKey );
+ //this.debug( 'searchAttribute', attrKey, attrVal, searchFor );
+ // bail if empty searchFor or unsearchable values
+ if( !searchFor
+ || ( attrVal === undefined || attrVal === null ) ){
+ return false;
+ }
+ // pass to sep. fn for deep search of array attributes
+ if( _.isArray( attrVal ) ){ return this._searchArrayAttribute( attrVal, searchFor ); }
+ return ( attrVal.toString().toLowerCase().indexOf( searchFor.toLowerCase() ) !== -1 );
+ },
+
+ /** deep(er) search for array attributes; T/F if found */
+ _searchArrayAttribute : function( array, searchFor ){
+ //this.debug( '_searchArrayAttribute', array, searchFor );
+ searchFor = searchFor.toLowerCase();
+ //precondition: searchFor has already been validated as non-empty string
+ //precondition: assumes only 1 level array
+ //TODO: could possibly break up searchFor more (CSV...)
+ return _.any( array, function( elem ){
+ return ( elem.toString().toLowerCase().indexOf( searchFor.toLowerCase() ) !== -1 );
+ });
+ },
+
+ /** search all searchAttributes for the string searchFor,
+ * returning a list of keys of attributes that contain searchFor
+ */
+ search : function( searchFor ){
+ var model = this;
+ return _.filter( this.searchAttributes, function( key ){
+ return model.searchAttribute( key, searchFor );
+ });
+ },
+
+ /** alias of search, but returns a boolean; accepts attribute specifiers where
+ * the attributes searched can be narrowed to a single attribute using
+ * the form: matches( 'genome_build=hg19' )
+ * (the attribute keys allowed can also be aliases to the true attribute key;
+ * see searchAliases above)
+ * @param {String} term plain text or ATTR_SPECIFIER sep. key=val pair
+ * @returns {Boolean} was term found in (any) attribute(s)
+ */
+ matches : function( term ){
+ var ATTR_SPECIFIER = '=',
+ split = term.split( ATTR_SPECIFIER );
+ // attribute is specified - search only that
+ if( split.length >= 2 ){
+ var attrKey = split[0];
+ attrKey = this.searchAliases[ attrKey ] || attrKey;
+ return this.searchAttribute( attrKey, split[1] );
+ }
+ // no attribute is specified - search all attributes in searchAttributes
+ return !!this.search( term ).length;
+ },
+
+ /** an implicit AND search for all terms; IOW, a model must match all terms given
+ * where terms is a whitespace separated value string.
+ * e.g. given terms of: 'blah bler database=hg19'
+ * an HDA would have to have attributes containing blah AND bler AND a genome_build == hg19
+ * To include whitespace in terms: wrap the term in double quotations (name="blah bler").
+ */
+ matchesAll : function( terms ){
+ var model = this;
+ // break the terms up by whitespace and filter out the empty strings
+ terms = terms.match( /(".*"|\w*=".*"|\S*)/g ).filter( function( s ){ return !!s; });
+ return _.all( terms, function( term ){
+ term = term.replace( /"/g, '' );
+ return model.matches( term );
+ });
+ }
+};
+
+
+//==============================================================================
+/** A view that renders hidden and shows when some activator is clicked.
+ * options:
+ * showFn: the effect used to show/hide the View (defaults to jq.toggle)
+ * $elementShown: some jqObject (defaults to this.$el) to be shown/hidden
+ * onShowFirstTime: fn called the first time the view is shown
+ * onshow: fn called every time the view is shown
+ * onhide: fn called every time the view is hidden
+ * events:
+ * hiddenUntilActivated:shown (the view is passed as an arg)
+ * hiddenUntilActivated:hidden (the view is passed as an arg)
+ * instance vars:
+ * view.hidden {boolean} is the view in the hidden state
+ */
var HiddenUntilActivatedViewMixin = /** @lends hiddenUntilActivatedMixin# */{
//TODO: since this is a mixin, consider moving toggle, hidden into HUAVOptions
- /** */
+ /** call this in your initialize to set up the mixin
+ * @param {jQuery} $activator the 'button' that's clicked to show/hide the view
+ * @param {Object} hash with mixin options
+ */
hiddenUntilActivated : function( $activator, options ){
// call this in your view's initialize fn
options = options || {};
+//TODO: flesh out options - show them all here
this.HUAVOptions = {
$elementShown : this.$el,
showFn : jQuery.prototype.toggle,
@@ -172,12 +288,15 @@
}
},
+//TODO:?? remove? use .hidden?
+ /** returns T/F if the view is hidden */
isHidden : function(){
return ( this.HUAVOptions.$elementShown.is( ':hidden' ) );
},
- /** */
+ /** toggle the hidden state, show/hide $elementShown, call onshow/hide, trigger events */
toggle : function(){
+//TODO: more specific name - toggle is too general
// can be called manually as well with normal toggle arguments
//TODO: better as a callback (when the show/hide is actually done)
// show
@@ -207,60 +326,101 @@
}
};
+
//==============================================================================
+/** 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 ){
- // 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 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.log( '\t expanded:', this.expanded );
},
// ........................................................................ render main
-//TODO: for lack of a better place, add rendering logic here
+ /** jq fx speed */
fxSpeed : 'fast',
/** Render this content, set up ui.
- * @param {Integer} speed the speed of the render
- * @fires rendered when rendered
- * @fires rendered:ready when first rendered and NO running HDAs
- * @returns {Object} this HDABaseView
+ * @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.skeleton( this.model.toJSON() ) );
+ var $newRender = $( this.templates.el( this.model.toJSON(), this ) );
if( this.expanded ){
- $newRender.children( '.details' ).replaceWith( this._renderDetails() );
+ this.$details( $newRender ).replaceWith( this._renderDetails().show() );
}
- this._setUpBehaviors( $newRender );
return $newRender;
},
- /** Fade out the old el, replace with new dom, then fade in.
- * @param {Boolean} fade whether or not to fade out/in when re-rendering
+ /** 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
- * @fires rendered:ready when first rendered and NO running HDAs
*/
_queueNewRender : function( $newRender, speed ) {
speed = ( speed === undefined )?( this.fxSpeed ):( speed );
@@ -280,6 +440,7 @@
]);
},
+ /** 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() );
},
@@ -287,21 +448,29 @@
/** 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;
+ _setUpBehaviors : function( $where ){
+ $where = $where || this.$el;
// set up canned behavior on children (bootstrap, popupmenus, editable_text, etc.)
- make_popup_menus( $container );
- $container.find( '[title]' ).tooltip({ placement : 'bottom' });
+ 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(){
- // override this
- return null;
+ var $newDetails = $( this.templates.details( this.model.toJSON(), this ) );
+ this._setUpBehaviors( $newDetails );
+ return $newDetails;
},
// ......................................................................... expansion/details
- /** Show or hide the body/details of history content.
+ /** Show or hide the details
* @param {Boolean} expand if true, expand; if false, collapse
*/
toggleExpanded : function( expand ){
@@ -320,26 +489,26 @@
*/
expand : function(){
var view = this;
-
- function _renderDetailsAndExpand(){
- view.$( '.details' ).replaceWith( view._renderDetails() );
- // needs to be set after the above or the slide will not show
- view.expanded = true;
- view.$( '.details' ).slideDown( view.fxSpeed, function(){
+ 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();
}
-//TODO:?? remove
- // fetch first if no details in the model
- if( !view.model.hasDetails() ){
- // we need the change event on HDCA's for the elements to be processed - so silent == false
- view.model.fetch().always( function( model ){
- _renderDetailsAndExpand();
- });
-//TODO: no error handling
- } else {
- _renderDetailsAndExpand();
- }
+ return jQuery.when();
},
/** Hide the body/details of an HDA.
@@ -348,19 +517,378 @@
collapse : function(){
var view = this;
view.expanded = false;
- this.$( '.details' ).slideUp( view.fxSpeed, function(){
+ 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.
+ * dataTransfer data is set to the JSON string of the view's model.toJSON
+ * Override '$dragHandle' to define the draggable DOM sub-element.
+ */
+var DraggableViewMixin = {
+
+ /** set up instance vars to track whether this view is currently draggable */
+ initialize : function( attributes ){
+ /** is the body of this hda view expanded/not? */
+ this.draggable = attributes.draggable || false;
+ },
+
+ /** what part of the view's DOM triggers the dragging */
+ $dragHandle : function(){
+//TODO: make abstract/general - move this to listItem
+ // override to the element you want to be your view's handle
+ return this.$( '.title-bar' );
+ },
+
+ /** toggle whether this view is draggable */
+ toggleDraggable : function(){
+ if( this.draggable ){
+ this.draggableOff();
+ } else {
+ this.draggableOn();
+ }
+ },
+
+ /** allow the view to be dragged, set up event handlers */
+ 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( '.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 handle = this.$dragHandle().attr( 'draggable', true ).get(0);
+ handle.addEventListener( 'dragstart', this.dragStartHandler, false );
+ handle.addEventListener( 'dragend', this.dragEndHandler, false );
+ },
+
+ /** turn of view dragging and remove event listeners */
+ draggableOff : function(){
+ this.draggable = false;
+ var handle = this.$dragHandle().attr( 'draggable', false ).get(0);
+ handle.removeEventListener( 'dragstart', this.dragStartHandler, false );
+ handle.removeEventListener( 'dragend', this.dragEndHandler, false );
+ },
+
+ /** sets the dataTransfer data to the model's toJSON
+ * @fires dragstart (bbone event) which is passed this view
+ */
+ _dragStartHandler : function( event ){
+ //this.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;
+ },
+
+ /** handle the dragend
+ * @fires dragend (bbone event) which is passed this view
+ */
+ _dragEndHandler : function( event ){
+ this.trigger( 'dragend', this );
+ //this.debug( 'dragEndHandler:', event )
+ return false;
+ }
+};
+
+
+//==============================================================================
+/** Mixin that allows a view to be selected (gen. from a list).
+ * Selection controls ($selector) may be hidden/shown/toggled.
+ * The bbone event 'selectable' is fired when the controls are shown/hidden (passed T/F).
+ * Default rendering is a font-awesome checkbox.
+ * Default selector is '.selector' within the view's $el.
+ * The bbone events 'selected' and 'de-selected' are fired when the $selector is clicked.
+ * Both events are passed the view and the (jQuery) event.
+ */
+var SelectableViewMixin = {
+
+ /** Set up instance state vars for whether the selector is shown and whether the view has been selected */
+ initialize : function( attributes ){
+ /** is the view currently in selection mode? */
+ this.selectable = attributes.selectable || false;
+ /** is the view currently selected? */
+ this.selected = attributes.selected || false;
+ },
+
+ /** $el sub-element where the selector is rendered and what can be clicked to select. */
+ $selector : function(){
+ return this.$( '.selector' );
+ },
+
+ /** How the selector is rendered - defaults to font-awesome checkbox */
+ _renderSelected : function(){
+ // override
+ this.$selector().find( 'span' )
+ .toggleClass( 'fa-check-square-o', this.selected ).toggleClass( 'fa-square-o', !this.selected );
+ },
+
+ /** Toggle whether the selector is shown */
+ toggleSelector : function(){
+//TODO: use this.selectable
+ if( !this.$selector().is( ':visible' ) ){
+ this.showSelector();
+ } else {
+ this.hideSelector();
+ }
+ },
+
+ /** Display the selector control.
+ * @param {Number} a jQuery fx speed
+ * @fires: selectable which is passed true (IOW, the selector is shown) and the view
+ */
+ showSelector : function( speed ){
+ speed = speed !== undefined? speed : this.fxSpeed;
+ // make sure selected state is represented properly
+ this.selectable = true;
+ this.trigger( 'selectable', true, this );
+ this._renderSelected();
+ this.$selector().show( speed );
+ },
+
+ /** remove the selector control
+ * @param {Number} a jQuery fx speed
+ * @fires: selectable which is passed false (IOW, the selector is not shown) and the view
+ */
+ hideSelector : function( speed ){
+ speed = speed !== undefined? speed : this.fxSpeed;
+ // reverse the process from showSelect
+ this.selectable = false;
+ this.trigger( 'selectable', false, this );
+ this.$selector().hide( speed );
+ },
+
+ /** Toggle whether the view is selected */
+ toggleSelect : function( event ){
+ if( this.selected ){
+ this.deselect( event );
+ } else {
+ this.select( event );
+ }
+ },
+
+ /** Select this view and re-render the selector control to show it
+ * @param {Event} a jQuery event that caused the selection
+ * @fires: selected which is passed the view and the DOM event that triggered it (optionally)
+ */
+ select : function( event ){
+ // switch icon, set selected, and trigger event
+ if( !this.selected ){
+ this.trigger( 'selected', this, event );
+ this.selected = true;
+ this._renderSelected();
+ }
+ return false;
+ },
+
+ /** De-select this view and re-render the selector control to show it
+ * @param {Event} a jQuery event that caused the selection
+ * @fires: de-selected which is passed the view and the DOM event that triggered it (optionally)
+ */
+ deselect : function( event ){
+ // switch icon, set selected, and trigger event
+ if( this.selected ){
+ this.trigger( 'de-selected', this, event );
+ this.selected = false;
+ this._renderSelected();
+ }
+ return false;
+ }
+};
+
+
+//==============================================================================
+/** 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.
+ *
+ * Details are rendered when the user clicks the title bar or presses enter/space when
+ * the title bar is in focus.
+ *
+ * Designed as a base class for history panel contents - but usable elsewhere (I hope).
+ */
+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
+ };
+}());
+
+
//==============================================================================
return {
LoggableMixin : LoggableMixin,
SessionStorageModel : SessionStorageModel,
+ SearchableModelMixin : SearchableModelMixin,
HiddenUntilActivatedViewMixin : HiddenUntilActivatedViewMixin,
mixin : mixin,
- ExpandableView : ExpandableView
+ wrapTemplate : wrapTemplate,
+ ExpandableView : ExpandableView,
+ DraggableViewMixin : DraggableViewMixin,
+ SelectableViewMixin : SelectableViewMixin,
+ ListItemView : ListItemView
};
});
diff -r bdc4017c2e7e9ecb5dfa3d36798f535402ec80aa -r 700560884da71e8bd3bdad6ddbe4edfde9566f7f static/scripts/mvc/collection/collection-model.js
--- a/static/scripts/mvc/collection/collection-model.js
+++ b/static/scripts/mvc/collection/collection-model.js
@@ -1,29 +1,57 @@
define([
- "mvc/dataset/hda-model",
+ "mvc/dataset/dataset-model",
"mvc/base-mvc",
"utils/localization"
-], function( HDA_MODEL, BASE_MVC, _l ){
+], function( DATASET, 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
+/*
+Notes:
+
+Terminology:
+ DatasetCollection/DC : a container of datasets or nested DatasetCollections
+ Element/DatasetCollectionElement/DCE : an item contained in a DatasetCollection
+ HistoryDatasetCollectionAssociation/HDCA: a DatasetCollection contained in a history
+
+
+This all seems too complex unfortunately:
+
+- Terminology collision between DatasetCollections (DCs) and Backbone Collections.
+- In the DatasetCollections API JSON, DC Elements use a 'Has A' stucture to *contain*
+ either a dataset or a nested DC. This would make the hierarchy much taller. I've
+ decided to merge the contained JSON with the DC element json - making the 'has a'
+ relation into an 'is a' relation. This seems simpler to me and allowed a lot of
+ DRY in both models and views, but may make tracking or tracing within these models
+ more difficult (since DatasetCollectionElements are now *also* DatasetAssociations
+ or DatasetCollections (nested)). This also violates the rule of thumb about
+ favoring aggregation over inheritance.
+- Currently, there are three DatasetCollection subclasses: List, Pair, and ListPaired.
+ These each should a) be usable on their own, b) be usable in the context of
+ nesting within a collection model (at least in the case of ListPaired), and
+ c) be usable within the context of other container models (like History or
+ LibraryFolder, etc.). I've tried to separate/extract classes in order to
+ handle those three situations, but it's proven difficult to do in a simple,
+ readable manner.
+- Ideally, histories and libraries would inherit from the same server models as
+ dataset collections do since they are (in essence) dataset collections themselves -
+ making the whole nested structure simpler. This would be a large, error-prone
+ refactoring and migration.
+
+Many of the classes and heirarchy are meant as extension points so, while the
+relations and flow may be difficult to understand initially, they'll allow us to
+handle the growth or flux dataset collection in the future (w/o actually implementing
+any YAGNI).
+
+*/
+//_________________________________________________________________________________________________ ELEMENTS
+/** @class mixin for Dataset collection elements.
+ * When collection elements are passed from the API, the underlying element is
+ * in a sub-object 'object' (IOW, a DCE representing an HDA will have HDA json in element.object).
+ * This mixin uses the constructor and parse methods to merge that JSON with the DCE attribtues
+ * effectively changing a DCE from a container to a subclass.
*/
-var DatasetCollectionElement = Backbone.Model.extend( BASE_MVC.LoggableMixin ).extend(
-/** @lends DatasetCollectionElement.prototype */{
+var DatasetCollectionElementMixin = {
- //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,
-
+ /** default attributes used by elements in a dataset collection */
defaults : {
model_class : 'DatasetCollectionElement',
element_identifier : null,
@@ -31,137 +59,52 @@
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(){
- //this.log( 'change:object' );
-//TODO: prob. better to update the sub-model instead of re-creating it
- this.object = this._createObjectModel();
- });
+ /** merge the attributes of the sub-object 'object' into this model */
+ _mergeObject : function( attributes ){
+ _.extend( attributes, attributes.object );
+ delete attributes.object;
+ return attributes;
},
- _createObjectModel : function(){
- //this.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' ),
- ObjectClass = this._getObjectClass();
- this.unset( 'object', { silent: true });
- this.object = new ObjectClass( object );
-
- return this.object;
+ /** override to merge this.object into this */
+ constructor : function( attributes, options ){
+ this.debug( '\t DatasetCollectionElement.constructor:', attributes, options );
+ attributes = this._mergeObject( attributes );
+ Backbone.Model.apply( this, arguments );
},
- _getObjectClass : function(){
- this.debug( 'DCE, element_type:', this.get( 'element_type' ) );
- switch( this.get( 'element_type' ) ){
- case 'dataset_collection':
- return DatasetCollection;
- case 'hda':
- return HDA_MODEL.HistoryDatasetAssociation;
- }
- throw new TypeError( 'Unknown element_type: ' + this.get( 'element_type' ) );
- },
+ /** when the model is fetched, merge this.object into this */
+ parse : function( response, options ){
+ var attributes = response;
+ attributes = this._mergeObject( attributes );
+ return attributes;
+ }
+};
- toJSON : function(){
- var json = Backbone.Model.prototype.toJSON.call( this );
- if( this.object ){
- json.object = this.object.toJSON();
- }
- return json;
- },
+//TODO: unused?
+/** Concrete class of Generic DatasetCollectionElement */
+var DatasetCollectionElement = Backbone.Model
+ .extend( BASE_MVC.LoggableMixin )
+ .extend( DatasetCollectionElementMixin );
- hasDetails : function(){
- return ( this.object !== null
- && this.object.hasDetails() );
- },
-
- /** String representation. */
- toString : function(){
- var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );
- return ([ 'DatasetCollectionElement(', objStr, ')' ].join( '' ));
- }
-});
-
-
+
//==============================================================================
-/** @class Backbone model for
- * @borrows LoggableMixin#logger as #logger
- * @borrows LoggableMixin#log as #log
- * @constructs
- */
-var HDADCE = DatasetCollectionElement.extend(
-/** @lends DatasetCollectionElement.prototype */{
-
- _getObjectClass : function(){
- return HDA_MODEL.HistoryDatasetAssociation;
- },
-
- /** String representation. */
- toString : function(){
- var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );
- return ([ 'HDADCE(', objStr, ')' ].join( '' ));
- }
-});
-
-
-//==============================================================================
-/** @class Backbone model for
- * @borrows LoggableMixin#logger as #logger
- * @borrows LoggableMixin#log as #log
- * @constructs
- */
-var DCDCE = DatasetCollectionElement.extend(
-/** @lends DatasetCollectionElement.prototype */{
-
- _getObjectClass : function(){
- return DatasetCollection;
- },
-
- getVisibleContents : function(){
- return this.object? this.object.getVisibleContents(): [];
- },
-
- /** String representation. */
- toString : function(){
- var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );
- return ([ 'DCDCE(', 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
- */
+/** @class Base/Abstract Backbone collection for Generic DCEs. */
var DCECollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend(
-/** @lends DatasetCollectionElementCollection.prototype */{
+/** @lends DCECollection.prototype */{
model: DatasetCollectionElement,
// comment this out to suppress log output
/** logger used to record this.log messages, commonly set to console */
//logger : console,
+//TODO: unused?
/** Set up.
* @see Backbone.Collection#initialize
*/
- initialize : function( models, options ){
+ initialize : function( attributes, options ){
+ this.debug( this + '(DCECollection).initialize:', attributes, options );
options = options || {};
- this.info( this + '.initialize:', models, options );
//this._setUpListeners();
},
@@ -173,69 +116,98 @@
//==============================================================================
-/** @class Backbone collection for
- * @borrows LoggableMixin#logger as #logger
- * @borrows LoggableMixin#log as #log
- * @constructs
+/** @class Backbone model for a dataset collection element that is a dataset (HDA).
*/
-var HDADCECollection = DCECollection.extend(
-/** @lends DatasetCollectionElementCollection.prototype */{
- model: HDADCE,
+var DatasetDCE = DATASET.DatasetAssociation.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,
+/** @lends DatasetDCE.prototype */{
+
+ /** logger used to record this.log messages, commonly set to console */
+ //logger : console,
+
+ defaults : _.extend( {}, DATASET.DatasetAssociation.prototype.defaults, DatasetCollectionElementMixin.defaults ),
+
+ // because all objects have constructors (as this hashmap would even if this next line wasn't present)
+ // the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model
+ // - re-apply manually it now
+ /** call the mixin constructor */
+ constructor : function( attributes, options ){
+ this.debug( '\t DatasetDCE.constructor:', attributes, options );
+ //DATASET.DatasetAssociation.prototype.constructor.call( this, attributes, options );
+ DatasetCollectionElementMixin.constructor.call( this, attributes, options );
+ },
+
+//TODO: unused?
+ /** set up */
+ initialize : function( attributes, options ){
+ this.debug( this + '(DatasetDCE).initialize:', attributes, options );
+ DATASET.DatasetAssociation.prototype.initialize.call( this, attributes, options );
+ },
/** String representation. */
toString : function(){
- return ([ 'HDADCECollection(', this.length, ')' ].join( '' ));
+ var objStr = this.get( 'element_identifier' );
+ return ([ 'DatasetDCE(', objStr, ')' ].join( '' ));
+ }
+}));
+
+
+//==============================================================================
+/** @class DCECollection of DatasetDCE's (a list of datasets, a pair of datasets).
+ */
+var DatasetDCECollection = DCECollection.extend(
+/** @lends DatasetDCECollection.prototype */{
+ model: DatasetDCE,
+
+ /** logger used to record this.log messages, commonly set to console */
+ //logger : console,
+
+//TODO: unused?
+ /** */
+ initialize : function( attributes, options ){
+ this.debug( this + '(DatasetDCECollection).initialize:', attributes, options );
+ DCECollection.prototype.initialize.call( this, attributes, options );
+ },
+
+ /** String representation. */
+ toString : function(){
+ return ([ 'DatasetDCECollection(', this.length, ')' ].join( '' ));
}
});
-//==============================================================================
-/** @class Backbone collection for
- * @borrows LoggableMixin#logger as #logger
- * @borrows LoggableMixin#log as #log
- * @constructs
+//_________________________________________________________________________________________________ COLLECTIONS
+/** @class Backbone model for Dataset Collections.
+ * The DC API returns an array of JSON objects under the attribute elements.
+ * This model:
+ * - removes that array/attribute ('elements') from the model,
+ * - creates a bbone collection (of the class defined in the 'collectionClass' attribute),
+ * - passes that json onto the bbone collection
+ * - caches the bbone collection in this.elements
*/
-var DCDCECollection = DCECollection.extend(
-/** @lends DatasetCollectionElementCollection.prototype */{
- model: DCDCE,
+var DatasetCollection = Backbone.Model
+ .extend( BASE_MVC.LoggableMixin )
+ .extend( BASE_MVC.SearchableModelMixin )
+.extend(/** @lends DatasetCollection.prototype */{
- /** String representation. */
- toString : function(){
- return ([ 'DCDCECollection(', 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,
+ /** logger used to record this.log messages, commonly set to console */
+ //logger : console,
/** default attributes for a model */
defaults : {
- collection_type : 'list'
+ /* 'list', 'paired', or 'list:paired' */
+ collection_type : null,
+ //??
+ deleted : false
},
+ /** Which class to use for elements */
collectionClass : DCECollection,
/** */
initialize : function( model, options ){
- this.info( 'DatasetCollection.initialize:', model, options );
+ this.debug( this + '(DatasetCollection).initialize:', model, options, this );
//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
@@ -245,15 +217,17 @@
/** move elements model attribute to full collection */
_createElementsModel : function(){
- this.log( '_createElementsModel', this.get( 'elements' ), this.elements );
+ this.debug( this + '._createElementsModel', this.collectionClass, 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 );
+ //this.debug( 'collectionClass:', this.collectionClass + '', this.elements );
return this.elements;
},
+ // ........................................................................ common queries
+ /** pass the elements back within the model json when this is serialized */
toJSON : function(){
var json = Backbone.Model.prototype.toJSON.call( this );
if( this.elements ){
@@ -262,17 +236,43 @@
return json;
},
+ /** is the collection done with updates and ready to be used? (finished running, etc.) */
+ inReadyState : function(){
+//TODO: state currenly unimplemented for collections
+ return true;
+ },
+
+ //TODO:?? the following are the same interface as DatasetAssociation - can we combine?
+ /** Does the DC contain any elements yet? Is a fetch() required? */
hasDetails : function(){
//TODO: this is incorrect for (accidentally) empty collections
this.debug( 'hasDetails:', this.elements.length );
return this.elements.length !== 0;
},
+ /** Given the filters, what models in this.elements would be returned? */
getVisibleContents : function( filters ){
- //TODO: filters unused for now
+ // filters unused for now
return this.elements;
},
+ // ........................................................................ ajax
+ /** save this dataset, _Mark_ing it as deleted (just a flag) */
+ 'delete' : function( options ){
+ if( this.get( 'deleted' ) ){ return jQuery.when(); }
+ return this.save( { deleted: true }, options );
+ },
+ /** save this dataset, _Mark_ing it as undeleted */
+ undelete : function( options ){
+ if( !this.get( 'deleted' ) || this.get( 'purged' ) ){ return jQuery.when(); }
+ return this.save( { deleted: false }, options );
+ },
+
+ // ........................................................................ searchable
+ searchAttributes : [
+ 'name'
+ ],
+
// ........................................................................ misc
/** String representation */
toString : function(){
@@ -283,10 +283,21 @@
//==============================================================================
+/** Model for a DatasetCollection containing datasets (non-nested).
+ */
var ListDatasetCollection = DatasetCollection.extend(
/** @lends ListDatasetCollection.prototype */{
- collectionClass : HDADCECollection,
+ /** logger used to record this.log messages, commonly set to console */
+ //logger : console,
+
+ collectionClass : DatasetDCECollection,
+
+//TODO: unused?
+ initialize : function( attrs, options ){
+ this.debug( this + '(ListDatasetCollection).initialize:', attrs, options );
+ DatasetCollection.prototype.initialize.call( this, attrs, options );
+ },
/** String representation. */
toString : function(){
@@ -296,8 +307,20 @@
//==============================================================================
+/** Model for a DatasetCollection containing fwd/rev datasets (a list of 2).
+ */
var PairDatasetCollection = ListDatasetCollection.extend(
-/** @lends ListDatasetCollection.prototype */{
+/** @lends PairDatasetCollection.prototype */{
+
+ /** logger used to record this.log messages, commonly set to console */
+ //logger : console,
+
+//TODO: unused?
+ /** */
+ initialize : function( attrs, options ){
+ this.debug( this + '(PairDatasetCollection).initialize:', attrs, options );
+ ListDatasetCollection.prototype.initialize.call( this, attrs, options );
+ },
/** String representation. */
toString : function(){
@@ -306,14 +329,129 @@
});
+//_________________________________________________________________________________________________ NESTED COLLECTIONS
+// this is where things get weird, man. Weird.
+//TODO: it might be possible to compact all the following...I think.
//==============================================================================
+/** @class Backbone model for a Generic DatasetCollectionElement that is also a DatasetCollection
+ * (a nested collection). Currently only list:paired.
+ */
+var NestedDCDCE = DatasetCollection.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,
+/** @lends NestedDCDCE.prototype */{
+
+ /** logger used to record this.log messages, commonly set to console */
+ //logger : console,
+
+ // because all objects have constructors (as this hashmap would even if this next line wasn't present)
+ // the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model
+ // - re-apply manually it now
+ /** call the mixin constructor */
+ constructor : function( attributes, options ){
+ this.debug( '\t NestedDCDCE.constructor:', attributes, options );
+ DatasetCollectionElementMixin.constructor.call( this, attributes, options );
+ },
+
+ /** String representation. */
+ toString : function(){
+ var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );
+ return ([ 'NestedDCDCE(', objStr, ')' ].join( '' ));
+ }
+}));
+
+
+//==============================================================================
+/** @class Backbone collection containing Generic NestedDCDCE's (nested dataset collections).
+ */
+var NestedDCDCECollection = DCECollection.extend(
+/** @lends NestedDCDCECollection.prototype */{
+
+ /** logger used to record this.log messages, commonly set to console */
+ //logger : console,
+
+ model: NestedDCDCE,
+
+//TODO: unused?
+ /** */
+ initialize : function( attrs, options ){
+ this.debug( this + '(NestedDCDCECollection).initialize:', attrs, options );
+ DCECollection.prototype.initialize.call( this, attrs, options );
+ },
+
+ /** String representation. */
+ toString : function(){
+ return ([ 'NestedDCDCECollection(', this.length, ')' ].join( '' ));
+ }
+});
+
+
+//==============================================================================
+/** @class Backbone model for a paired dataset collection within a list:paired dataset collection.
+ */
+var NestedPairDCDCE = PairDatasetCollection.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,
+/** @lends NestedPairDCDCE.prototype */{
+//TODO:?? possibly rename to NestedDatasetCollection?
+
+ /** logger used to record this.log messages, commonly set to console */
+ //logger : console,
+
+ /** */
+ constructor : function( attributes, options ){
+ this.debug( '\t NestedPairDCDCE.constructor:', attributes, options );
+ //DatasetCollection.constructor.call( this, attributes, options );
+ DatasetCollectionElementMixin.constructor.call( this, attributes, options );
+ },
+
+ /** String representation. */
+ toString : function(){
+ var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );
+ return ([ 'NestedPairDCDCE(', objStr, ')' ].join( '' ));
+ }
+}));
+
+
+//==============================================================================
+/** @class Backbone collection for a backbone collection containing paired dataset collections.
+ */
+var NestedPairDCDCECollection = NestedDCDCECollection.extend(
+/** @lends PairDCDCECollection.prototype */{
+
+ /** logger used to record this.log messages, commonly set to console */
+ //logger : console,
+
+ model: NestedPairDCDCE,
+
+//TODO: unused?
+ /** */
+ initialize : function( attrs, options ){
+ this.debug( this + '(NestedPairDCDCECollection).initialize:', attrs, options );
+ NestedDCDCECollection.prototype.initialize.call( this, attrs, options );
+ },
+
+ /** String representation. */
+ toString : function(){
+ return ([ 'NestedPairDCDCECollection(', this.length, ')' ].join( '' ));
+ }
+});
+
+
+//==============================================================================
+/** @class Backbone Model for a DatasetCollection (list) that contains DatasetCollections (pairs).
+ */
var ListPairedDatasetCollection = DatasetCollection.extend(
-/** @lends ListDatasetCollection.prototype */{
+/** @lends ListPairedDatasetCollection.prototype */{
- collectionClass : DCDCECollection,
+ /** logger used to record this.log messages, commonly set to console */
+ //logger : console,
// list:paired is the only collection that itself contains collections
- //collectionClass : DatasetCollectionCollection,
+ collectionClass : NestedPairDCDCECollection,
+
+//TODO: unused?
+ /** */
+ initialize : function( attributes, options ){
+ this.debug( this + '(ListPairedDatasetCollection).initialize:', attributes, options );
+ DatasetCollection.prototype.initialize.call( this, attributes, options );
+ },
/** String representation. */
toString : function(){
@@ -324,7 +462,6 @@
//==============================================================================
return {
- //DatasetCollection : DatasetCollection,
ListDatasetCollection : ListDatasetCollection,
PairDatasetCollection : PairDatasetCollection,
ListPairedDatasetCollection : ListPairedDatasetCollection
diff -r bdc4017c2e7e9ecb5dfa3d36798f535402ec80aa -r 700560884da71e8bd3bdad6ddbe4edfde9566f7f static/scripts/mvc/collection/collection-panel.js
--- a/static/scripts/mvc/collection/collection-panel.js
+++ b/static/scripts/mvc/collection/collection-panel.js
@@ -21,7 +21,6 @@
//MODEL is either a DatasetCollection (or subclass) or a DatasetCollectionElement (list of pairs)
/** logger used to record this.log messages, commonly set to console */
- // comment this out to suppress log output
//logger : console,
tagName : 'div',
@@ -30,7 +29,8 @@
/** (in ms) that jquery effects will use */
fxSpeed : 'fast',
- DCEViewClass : DC_BASE.DCEBaseView,
+ DatasetDCEViewClass : DC_BASE.DatasetDCEBaseView,
+ NestedDCEViewClass : DC_BASE.NestedDCEBaseView,
// ......................................................................... SET UP
/** Set up the view, set up storage, bind listeners to HistoryContents events
@@ -47,8 +47,6 @@
this.hasUser = attributes.hasUser;
this.panelStack = [];
this.parentName = attributes.parentName;
-
- window.collectionPanel = this;
},
/** create any event listeners for the panel
@@ -194,10 +192,8 @@
//this.debug( 'content json:', JSON.stringify( content, null, ' ' ) );
var contentView = null,
ContentClass = this._getContentClass( content );
- //this.debug( 'content.object json:', JSON.stringify( content.object, null, ' ' ) );
this.debug( 'ContentClass:', ContentClass );
- //this.debug( 'content:', content );
- this.debug( 'content.object:', content.object );
+ this.debug( 'content:', content );
contentView = new ContentClass({
model : content,
linkTarget : this.linkTarget,
@@ -213,6 +209,7 @@
/** */
_getContentClass : function( content ){
this.debug( this + '._getContentClass:', content );
+ this.debug( 'DCEViewClass:', this.DCEViewClass );
switch( content.get( 'element_type' ) ){
case 'hda':
return this.DCEViewClass;
@@ -336,7 +333,9 @@
// =============================================================================
/** @class non-editable, read-only View/Controller for a dataset collection. */
var ListCollectionPanel = CollectionPanel.extend({
- DCEViewClass : DC_BASE.HDADCEBaseView,
+
+ DCEViewClass : DC_BASE.DatasetDCEBaseView,
+
// ........................................................................ misc
/** string rep */
toString : function(){
@@ -348,6 +347,7 @@
// =============================================================================
/** @class non-editable, read-only View/Controller for a dataset collection. */
var PairCollectionPanel = ListCollectionPanel.extend({
+
// ........................................................................ misc
/** string rep */
toString : function(){
@@ -359,7 +359,9 @@
// =============================================================================
/** @class non-editable, read-only View/Controller for a dataset collection. */
var ListOfPairsCollectionPanel = CollectionPanel.extend({
- DCEViewClass : DC_BASE.DCDCEBaseView,
+
+ DCEViewClass : DC_BASE.NestedDCDCEBaseView,
+
// ........................................................................ misc
/** string rep */
toString : function(){
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.
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/5f663734a26e/
Changeset: 5f663734a26e
User: natefoo
Date: 2014-08-07 17:35:20
Summary: Bug fix for display site changes.
Affected #: 3 files
diff -r 103697ebdd339377424063fba0459cb3cc2b707c -r 5f663734a26e8e2d1c462320741c86c9b5d9aef6 lib/galaxy/datatypes/genetics.py
--- a/lib/galaxy/datatypes/genetics.py
+++ b/lib/galaxy/datatypes/genetics.py
@@ -85,7 +85,7 @@
if not dataset.dbkey:
dataset.dbkey = 'hg18' # punt!
if dataset.has_data():
- for site_name, site_url in app.build_sites.get_ucsc_by_build(dataset.dbkey):
+ for site_name, site_url in app.build_sites.get_ucsc_sites_by_build(dataset.dbkey):
if site_name in app.config.ucsc_display_sites:
site_url = site_url.replace('/hgTracks?','/hgGenome?') # for genome graphs
internal_url = "%s" % url_for( controller='dataset',
diff -r 103697ebdd339377424063fba0459cb3cc2b707c -r 5f663734a26e8e2d1c462320741c86c9b5d9aef6 lib/galaxy/datatypes/interval.py
--- a/lib/galaxy/datatypes/interval.py
+++ b/lib/galaxy/datatypes/interval.py
@@ -234,7 +234,7 @@
# Filter UCSC sites to only those that are supported by this build and
# enabled.
valid_sites = [ ( name, url )
- for name, url in app.build_sites.get_ucsc_by_build( dataset.dbkey )
+ for name, url in app.build_sites.get_ucsc_sites_by_build( dataset.dbkey )
if name in app.config.ucsc_display_sites ]
if not valid_sites:
return []
@@ -750,7 +750,7 @@
ret_val = []
seqid, start, stop = self.get_estimated_display_viewport( dataset )
if seqid is not None:
- for site_name, site_url in app.build_sites.get_ucsc_by_build( dataset.dbkey ):
+ for site_name, site_url in app.build_sites.get_ucsc_sites_by_build( dataset.dbkey ):
if site_name in app.config.ucsc_display_sites:
redirect_url = urllib.quote_plus(
"%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" %
@@ -1103,7 +1103,7 @@
ret_val = []
chrom, start, stop = self.get_estimated_display_viewport( dataset )
if chrom is not None:
- for site_name, site_url in app.build_sites.get_ucsc_by_build( dataset.dbkey ):
+ for site_name, site_url in app.build_sites.get_ucsc_sites_by_build( dataset.dbkey ):
if site_name in app.config.ucsc_display_sites:
redirect_url = urllib.quote_plus( "%sdb=%s&position=%s:%s-%s&hgt.customText=%%s" % ( site_url, dataset.dbkey, chrom, start, stop ) )
link = self._get_remote_call_url( redirect_url, site_name, dataset, type, app, base_url )
@@ -1285,7 +1285,7 @@
ret_val = []
chrom, start, stop = self.get_estimated_display_viewport(dataset)
if chrom is not None:
- for site_name, site_url in app.build_sites.get_ucsc_by_build(dataset.dbkey):
+ for site_name, site_url in app.build_sites.get_ucsc_sites_by_build(dataset.dbkey):
if site_name in app.config.ucsc_display_sites:
internal_url = "%s" % url_for( controller='dataset', dataset_id=dataset.id, action='display_at', filename='ucsc_' + site_name )
display_url = urllib.quote_plus( "%s%s/display_as?id=%i&display_app=%s&authz_method=display_at" % (base_url, url_for( controller='root' ), dataset.id, type) )
diff -r 103697ebdd339377424063fba0459cb3cc2b707c -r 5f663734a26e8e2d1c462320741c86c9b5d9aef6 lib/galaxy/util/build_sites.py
--- a/lib/galaxy/util/build_sites.py
+++ b/lib/galaxy/util/build_sites.py
@@ -38,7 +38,7 @@
self._build_sites['ucsc'] = self.read_build_sites( self._app.config.ucsc_build_sites )
self._build_sites['gbrowse'] = self.read_build_sites( self._app.config.gbrowse_build_sites )
- def get_site_by_build( self, site_type, build ):
+ def _get_site_by_build( self, site_type, build ):
sites = []
for site in self._build_sites[site_type]:
if build in site['builds']:
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.