From b8fb01b2ccf39624c333de08844ffbb5895ee334 Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Tue, 6 Nov 2012 15:54:47 -0500 Subject: [PATCH] (alt)history: split files up, rework names, add global Galaxy namespace --- .../mvc/{history.js => dataset/hda-edit.js} | 755 +----------------- static/scripts/mvc/dataset/hda-model.js | 223 ++++++ static/scripts/mvc/history/history-model.js | 180 +++++ static/scripts/mvc/history/history-panel.js | 380 +++++++++ templates/root/alternate_history.mako | 114 ++- 5 files changed, 858 insertions(+), 794 deletions(-) rename static/scripts/mvc/{history.js => dataset/hda-edit.js} (55%) create mode 100644 static/scripts/mvc/dataset/hda-model.js create mode 100644 static/scripts/mvc/history/history-model.js create mode 100644 static/scripts/mvc/history/history-panel.js diff --git a/static/scripts/mvc/history.js b/static/scripts/mvc/dataset/hda-edit.js similarity index 55% rename from static/scripts/mvc/history.js rename to static/scripts/mvc/dataset/hda-edit.js index 61c834eae94..4c05bef9e46 100644 --- a/static/scripts/mvc/history.js +++ b/static/scripts/mvc/dataset/hda-edit.js @@ -1,479 +1,10 @@ //define([ // "../mvc/base-mvc" -// //], function(){ -/* ============================================================================= -Backbone.js implementation of history panel - -TODO: - bug: - anon, mako: - tooltips not rendered - anno, tags rendered - title editable - bug: - when over quota history is re-rendered, over quota msg is not displayed - bc the quota:over event isn't fired - bc the user state hasn't changed - - anon user, mako template init: - bug: rename url seems to be wrong url - - currently, adding a dataset (via tool execute, etc.) creates a new dataset and refreshes the page - logged in, mako template: - BUG: am able to start upload even if over quota - 'runs' forever - - BUG: from above sit, delete uploading hda - now in state 'discarded'! ...new state to handle - bug: quotaMeter bar rendering square in chrome - BUG: quotaMsg not showing when 100% (on load) - BUG: upload, history size, doesn't change - TODO: on hdas state:ready, update ONLY the size...from what? histories.py? in js? - BUG: imported, shared history with unaccessible dataset errs in historycontents when getting history - (entire history is inaccessible) - ??: still happening? - - from loadFromApi: - BUG: not showing previous annotations - - fixed: - BUG: historyItem, error'd ds show display, download? - FIXED: removed - bug: loading hdas (alt_hist) - FIXED: added anon user api request ( trans.user == None and trans.history.id == requested id ) - bug: quota meter not updating on upload/tool run - FIXED: quotaMeter now listens for 'state:ready' from glx_history in alternate_history.mako - bug: use of new HDACollection with event listener in init doesn't die...keeps reporting - FIXED: change getVisible to return an array - BUG: history, broken intial hist state (running, updater, etc.) - ??: doesn't seem to happen anymore - BUG: collapse all should remove all expanded from storage - FIXED: hideAllItemBodies now resets storage.expandedItems - BUG: historyItem, shouldn't allow tag, annotate, peek on purged datasets - FIXED: ok state now shows only: info, rerun - BUG: history?, some ids aren't returning encoded... - FIXED:??? - BUG: history, showing deleted ds - FIXED - UGH: historyItems have to be decorated with history_ids (api/histories/:history_id/contents/:id) - FIXED by adding history_id to history_contents.show - BUG: history, if hist has err'd ds, hist has perm state 'error', updater on following ds's doesn't run - FIXED by reordering history state from ds' states here and histories - BUG: history, broken annotation on reload (can't get thru api (sets fine, tho)) - FIXED: get thru api for now - - to relational model? - HDACollection, meta_files, display_apps, etc. - - - quota mgr - show_deleted/hidden: - use storage - on/off ui - move histview fadein/out in render to app? - don't draw body until it's first expand event - localize all - break this file up - ?: render url templates on init or render? - ?: history, annotation won't accept unicode - - hierarchy: - dataset -> hda - history -> historyForEditing, historyForViewing - display_structured? - - btw: get an error'd ds by running fastqc on fastq (when you don't have it installed) - - meta: - css/html class/id 'item' -> hda - add classes, ids on empty divs - events (local/ui and otherwise) - list in docs as well - require.js - convert function comments to jsDoc style, complete comments - move inline styles into base.less - watch the magic strings - watch your globals - - features: - lineage - hide button - show permissions in info - show shared/sharing status on ds, history - maintain scroll position on refresh (storage?) - selection, multi-select (and actions common to selected (ugh)) - searching - sorting, re-shuffling - -============================================================================= */ -var HistoryDatasetAssociation = BaseModel.extend( LoggableMixin ).extend({ - // a single HDA model - - // uncomment this out see log messages - //logger : console, - - defaults : { - // ---these are part of an HDA proper: - - // parent (containing) history - history_id : null, - // often used with tagging - model_class : 'HistoryDatasetAssociation', - // index within history (??) - hid : 0, - - // ---whereas these are Dataset related/inherited - - id : null, - name : '', - // one of HistoryDatasetAssociation.STATES - state : '', - // sniffed datatype (sam, tabular, bed, etc.) - data_type : null, - // size in bytes - file_size : 0, - - // array of associated file types (eg. [ 'bam_index', ... ]) - meta_files : [], - - misc_blurb : '', - misc_info : '', - - deleted : false, - purged : false, - // aka. !hidden - visible : false, - // based on trans.user (is_admin or security_agent.can_access_dataset( , hda.dataset )) - accessible : false, - - //TODO: this needs to be removed (it is a function of the view type (e.g. HDAForEditingView)) - for_editing : true - }, - - // fetch location of this history in the api - url : function(){ - //TODO: get this via url router - return 'api/histories/' + this.get( 'history_id' ) + '/contents/' + this.get( 'id' ); - }, - - // (curr) only handles changing state of non-accessible hdas to STATES.NOT_VIEWABLE - //TODO:? use initialize (or validate) to check purged AND deleted -> purged XOR deleted - initialize : function(){ - this.log( this + '.initialize', this.attributes ); - this.log( '\tparent history_id: ' + this.get( 'history_id' ) ); - - //!! this state is not in trans.app.model.Dataset.states - set it here - - //TODO: change to server side. - if( !this.get( 'accessible' ) ){ - this.set( 'state', HistoryDatasetAssociation.STATES.NOT_VIEWABLE ); - } - - // if the state has changed and the new state is a ready state, fire an event - this.on( 'change:state', function( currModel, newState ){ - this.log( this + ' has changed state:', currModel, newState ); - if( this.inReadyState() ){ - this.trigger( 'state:ready', this.get( 'id' ), newState, this.previous( 'state' ), currModel ); - } - }); - - // debug on change events - //this.on( 'change', function( currModel, changedList ){ - // this.log( this + ' has changed:', currModel, changedList ); - //}); - //this.bind( 'all', function( event ){ - // this.log( this + '', arguments ); - //}); - }, - - isDeletedOrPurged : function(){ - return ( this.get( 'deleted' ) || this.get( 'purged' ) ); - }, - - // based on show_deleted, show_hidden (gen. from the container control), would this ds show in the list of ds's? - //TODO: too many visibles - isVisible : function( show_deleted, show_hidden ){ - var isVisible = true; - if( ( !show_deleted ) - && ( this.get( 'deleted' ) || this.get( 'purged' ) ) ){ - isVisible = false; - } - if( ( !show_hidden ) - && ( !this.get( 'visible' ) ) ){ - isVisible = false; - } - return isVisible; - }, - - // 'ready' states are states where no processing (for the ds) is left to do on the server - inReadyState : function(){ - var state = this.get( 'state' ); - return ( - ( state === HistoryDatasetAssociation.STATES.NEW ) - || ( state === HistoryDatasetAssociation.STATES.OK ) - || ( state === HistoryDatasetAssociation.STATES.EMPTY ) - || ( state === HistoryDatasetAssociation.STATES.FAILED_METADATA ) - || ( state === HistoryDatasetAssociation.STATES.NOT_VIEWABLE ) - || ( state === HistoryDatasetAssociation.STATES.DISCARDED ) - || ( state === HistoryDatasetAssociation.STATES.ERROR ) - ); - }, - - // convenience fn to match hda.has_data - hasData : function(){ - //TODO:?? is this equivalent to all possible hda.has_data calls? - return ( this.get( 'file_size' ) > 0 ); - }, - - toString : function(){ - var nameAndId = this.get( 'id' ) || ''; - if( this.get( 'name' ) ){ - nameAndId += ':"' + this.get( 'name' ) + '"'; - } - return 'HistoryDatasetAssociation(' + nameAndId + ')'; - } -}); - -//------------------------------------------------------------------------------ -HistoryDatasetAssociation.STATES = { - UPLOAD : 'upload', - QUEUED : 'queued', - RUNNING : 'running', - SETTING_METADATA : 'setting_metadata', - - NEW : 'new', - OK : 'ok', - EMPTY : 'empty', - - FAILED_METADATA : 'failed_metadata', - NOT_VIEWABLE : 'noPermission', // not in trans.app.model.Dataset.states - DISCARDED : 'discarded', - ERROR : 'error' -}; - -//============================================================================== -var HDACollection = Backbone.Collection.extend( LoggableMixin ).extend({ - model : HistoryDatasetAssociation, - - //logger : console, - - initialize : function(){ - //this.bind( 'all', function( event ){ - // this.log( this + '', arguments ); - //}); - }, - - // return the ids of every hda in this collection - ids : function(){ - return this.map( function( item ){ return item.id; }); - }, - - // return an HDA collection containing every 'shown' hda based on show_deleted/hidden - getVisible : function( show_deleted, show_hidden ){ - return this.filter( function( item ){ return item.isVisible( show_deleted, show_hidden ); }); - }, - - // get a map where : [ ] - getStateLists : function(){ - var stateLists = {}; - _.each( _.values( HistoryDatasetAssociation.STATES ), function( state ){ - stateLists[ state ] = []; - }); - //NOTE: will err on unknown state - this.each( function( item ){ - stateLists[ item.get( 'state' ) ].push( item.get( 'id' ) ); - }); - return stateLists; - }, - - // returns the id of every hda still running (not in a ready state) - running : function(){ - var idList = []; - this.each( function( item ){ - if( !item.inReadyState() ){ - idList.push( item.get( 'id' ) ); - } - }); - return idList; - }, - - // update (fetch -> render) the hdas with the ids given - update : function( ids ){ - this.log( this + 'update:', ids ); - - if( !( ids && ids.length ) ){ return; } - - var collection = this; - _.each( ids, function( id, index ){ - var historyItem = collection.get( id ); - historyItem.fetch(); - }); - }, - - toString : function(){ - return ( 'HDACollection(' + this.ids().join(',') + ')' ); - } -}); - - -//============================================================================== -var History = BaseModel.extend( LoggableMixin ).extend({ - //TODO: bind change events from items and collection to this (itemLengths, states) - - // uncomment this out see log messages - //logger : console, - - // values from api (may need more) - defaults : { - id : '', - name : '', - state : '', - - //TODO: wire these to items (or this) - show_deleted : false, - show_hidden : false, - - diskSize : 0, - deleted : false, - - tags : [], - annotation : null, - - //TODO: quota msg and message? how to get those over the api? - message : null, - quotaMsg : false - }, - - url : function(){ - // api location of history resource - //TODO: hardcoded - return 'api/histories/' + this.get( 'id' ); - }, - - initialize : function( initialSettings, initialHdas ){ - this.log( this + ".initialize:", initialSettings, initialHdas ); - - this.hdas = new HDACollection(); - - // if we've got hdas passed in the constructor, load them and set up updates if needed - if( initialHdas && initialHdas.length ){ - this.hdas.reset( initialHdas ); - this.checkForUpdates(); - } - - //this.on( 'change', function( currModel, changedList ){ - // this.log( this + ' has changed:', currModel, changedList ); - //}); - //this.bind( 'all', function( event ){ - // this.log( this + '', arguments ); - //}); - }, - - // get data via the api (alternative to sending options,hdas to initialize) - loadFromApi : function( historyId, callback ){ - var history = this; - - // fetch the history AND the user (mainly to see if they're logged in at this point) - history.attributes.id = historyId; - //TODO:?? really? fetch user here? - jQuery.when( jQuery.ajax( 'api/users/current' ), history.fetch() - - ).then( function( userResponse, historyResponse ){ - //console.warn( 'fetched user, history: ', userResponse, historyResponse ); - history.attributes.user = userResponse[0]; //? meh. - history.log( history ); - - }).then( function(){ - // ...then the hdas (using contents?ids=...) - jQuery.ajax( history.url() + '/contents?' + jQuery.param({ - ids : history.itemIdsFromStateIds().join( ',' ) - - // reset the collection to the hdas returned - })).success( function( hdas ){ - //console.warn( 'fetched hdas' ); - history.hdas.reset( hdas ); - history.checkForUpdates(); - callback(); - }); - }); - }, - - // reduce the state_ids map of hda id lists -> a single list of ids - //...ugh - seems roundabout; necessary because the history doesn't have a straightforward list of ids - // (and history_contents/index curr returns a summary only) - hdaIdsFromStateIds : function(){ - return _.reduce( _.values( this.get( 'state_ids' ) ), function( reduction, currIdList ){ - return reduction.concat( currIdList ); - }); - }, - - // get the history's state from it's cummulative ds states, delay + update if needed - checkForUpdates : function( datasets ){ - // get overall History state from collection, run updater if History has running/queued hdas - // boiling it down on the client to running/not - if( this.hdas.running().length ){ - this.stateUpdater(); - } - return this; - }, - - // update this history, find any hda's running/queued, update ONLY those that have changed states, - // set up to run this again in some interval of time - stateUpdater : function(){ - var history = this, - oldState = this.get( 'state' ), - // state ids is a map of every possible hda state, each containing a list of ids for hdas in that state - oldStateIds = this.get( 'state_ids' ); - - // pull from the history api - //TODO: fetch? - jQuery.ajax( 'api/histories/' + this.get( 'id' ) - - ).success( function( response ){ - //this.log( 'historyApiRequest, response:', response ); - history.set( response ); - history.log( 'current history state:', history.get( 'state' ), - '(was)', oldState, - 'new size:', history.get( 'nice_size' ) ); - - //TODO: revisit this - seems too elaborate, need something straightforward - // for each state, check for the difference between old dataset states and new - // the goal here is to check ONLY those datasets that have changed states (not all datasets) - var changedIds = []; - _.each( _.keys( response.state_ids ), function( state ){ - var diffIds = _.difference( response.state_ids[ state ], oldStateIds[ state ] ); - // aggregate those changed ids - changedIds = changedIds.concat( diffIds ); - }); - - // send the changed ids (if any) to dataset collection to have them fetch their own model changes - if( changedIds.length ){ - history.hdas.update( changedIds ); - } - - // set up to keep pulling if this history in run/queue state - //TODO: magic number here - if( ( history.get( 'state' ) === HistoryDatasetAssociation.STATES.RUNNING ) - || ( history.get( 'state' ) === HistoryDatasetAssociation.STATES.QUEUED ) ){ - setTimeout( function(){ - history.stateUpdater(); - }, 4000 ); - } - - }).error( function( xhr, status, error ){ - if( console && console.warn ){ - console.warn( 'Error getting history updates from the server:', xhr, status, error ); - } - alert( 'Error getting history updates from the server.\n' + error ); - }); - }, - - toString : function(){ - var nameString = ( this.get( 'name' ) )? - ( ',' + this.get( 'name' ) ) : ( '' ); - return 'History(' + this.get( 'id' ) + nameString + ')'; - } -}); - //============================================================================== +/** View for editing (working with - as opposed to viewing/read-only) an hda + * + */ var HDAView = BaseView.extend( LoggableMixin ).extend({ //??TODO: add alias in initialize this.hda = this.model? // view for HistoryDatasetAssociation model above @@ -1325,287 +856,7 @@ function create_trackster_action_fn(vis_url, dataset_params, dbkey) { }; } - -//============================================================================== -// view for the HDACollection (as per current right hand panel) -var HistoryView = BaseView.extend( LoggableMixin ).extend({ - - // uncomment this out see log messages - //logger : console, - - // direct attachment to existing element - el : 'body.historyPage', - - // init with the model, urlTemplates, set up storage, bind HDACollection events - //NOTE: this will create or load PersistantStorage keyed under 'HistoryView.' - //pre: you'll need to pass in the urlTemplates (urlTemplates : { history : {...}, hda : {...} }) - initialize : function( attributes ){ - this.log( this + '.initialize:', attributes ); - - // set up url templates - //TODO: prob. better to put this in class scope (as the handlebars templates), but... - // they're added to GalaxyPaths on page load (after this file is loaded) - if( !attributes.urlTemplates ){ throw( 'HDAView needs urlTemplates on initialize' ); } - if( !attributes.urlTemplates.history ){ throw( 'HDAView needs urlTemplates.history on initialize' ); } - if( !attributes.urlTemplates.hda ){ throw( 'HDAView needs urlTemplates.hda on initialize' ); } - this.urlTemplates = attributes.urlTemplates.history; - this.hdaUrlTemplates = attributes.urlTemplates.hda; - - // data that needs to be persistant over page refreshes - // (note the key function which uses the history id as well) - this.storage = new PersistantStorage( - 'HistoryView.' + this.model.get( 'id' ), - { expandedHdas : {} } - ); - - // bind events from the model's hda collection - //this.model.bind( 'change', this.render, this ); - this.model.bind( 'change:nice_size', this.updateHistoryDiskSize, this ); - - this.model.hdas.bind( 'add', this.add, this ); - this.model.hdas.bind( 'reset', this.addAll, this ); - this.model.hdas.bind( 'all', this.all, this ); - - //this.bind( 'all', function(){ - // this.log( arguments ); - //}, this ); - - // set up instance vars - this.hdaViews = {}; - this.urls = {}; - }, - - add : function( hda ){ - //console.debug( 'add.' + this, hda ); - //TODO - }, - - addAll : function(){ - //console.debug( 'addAll.' + this ); - // re render when all hdas are reset - this.render(); - }, - - all : function( event ){ - //console.debug( 'allItemEvents.' + this, event ); - //...for which to do the debuggings - }, - - // render the urls for this view using urlTemplates and the model data - renderUrls : function( modelJson ){ - var historyView = this; - - historyView.urls = {}; - _.each( this.urlTemplates, function( urlTemplate, urlKey ){ - historyView.urls[ urlKey ] = _.template( urlTemplate, modelJson ); - }); - return historyView.urls; - }, - - // render urls, historyView body, and hdas (if any are shown), fade out, swap, fade in, set up behaviours - render : function(){ - var historyView = this, - setUpQueueName = historyView.toString() + '.set-up', - newRender = $( '
' ), - modelJson = this.model.toJSON(), - initialRender = ( this.$el.children().size() === 0 ); - - //console.debug( this + '.render, initialRender:', initialRender ); - - // render the urls and add them to the model json - modelJson.urls = this.renderUrls( modelJson ); - - // render the main template, tooltips - //NOTE: this is done before the items, since item views should handle theirs themselves - newRender.append( HistoryView.templates.historyPanel( modelJson ) ); - newRender.find( '.tooltip' ).tooltip(); - - // render hda views (if any and any shown (show_deleted/hidden) - if( !this.model.hdas.length - || !this.renderItems( newRender.find( '#' + this.model.get( 'id' ) + '-datasets' ) ) ){ - // if history is empty or no hdas would be rendered, show the empty message - newRender.find( '#emptyHistoryMessage' ).show(); - } - - // fade out existing, swap with the new, fade in, set up behaviours - $( historyView ).queue( setUpQueueName, function( next ){ - historyView.$el.fadeOut( 'fast', function(){ next(); }); - }); - $( historyView ).queue( setUpQueueName, function( next ){ - // swap over from temp div newRender - historyView.$el.html( '' ); - historyView.$el.append( newRender.children() ); - - historyView.$el.fadeIn( 'fast', function(){ next(); }); - }); - $( historyView ).queue( setUpQueueName, function( next ){ - this.log( historyView + ' rendered:', historyView.$el ); - - //TODO: ideally, these would be set up before the fade in (can't because of async save text) - historyView.setUpBehaviours(); - - if( initialRender ){ - historyView.trigger( 'rendered:initial' ); - - } else { - historyView.trigger( 'rendered' ); - } - next(); - }); - $( historyView ).dequeue( setUpQueueName ); - return this; - }, - - // set up a view for each item to be shown, init with model and listeners, cache to map ( model.id : view ) - renderItems : function( $whereTo ){ - this.hdaViews = {}; - var historyView = this, - show_deleted = this.model.get( 'show_deleted' ), - show_hidden = this.model.get( 'show_hidden' ), - visibleHdas = this.model.hdas.getVisible( show_deleted, show_hidden ); - - // only render the shown hdas - _.each( visibleHdas, function( hda ){ - var hdaId = hda.get( 'id' ), - expanded = historyView.storage.get( 'expandedHdas' ).get( hdaId ); - historyView.hdaViews[ hdaId ] = new HDAView({ - model : hda, - expanded : expanded, - urlTemplates : historyView.hdaUrlTemplates - }); - historyView.setUpHdaListeners( historyView.hdaViews[ hdaId ] ); - // render it (NOTE: reverse order, newest on top (prepend)) - //TODO: by default send a reverse order list (although this may be more efficient - it's more confusing) - $whereTo.prepend( historyView.hdaViews[ hdaId ].render().$el ); - }); - return visibleHdas.length; - }, - - // set up HistoryView->HDAView listeners - setUpHdaListeners : function( hdaView ){ - var historyView = this; - - // use storage to maintain a list of hdas whose bodies are expanded - hdaView.bind( 'toggleBodyVisibility', function( id, visible ){ - if( visible ){ - historyView.storage.get( 'expandedHdas' ).set( id, true ); - } else { - historyView.storage.get( 'expandedHdas' ).deleteKey( id ); - } - }); - - // rendering listeners - hdaView.bind( 'rendered:ready', function(){ historyView.trigger( 'hda:rendered:ready' ); }); - }, - - // set up js/widget behaviours: tooltips, - //TODO: these should be either sub-MVs, or handled by events - setUpBehaviours : function(){ - // anon users shouldn't have access to any of these - if( !( this.model.get( 'user' ) && this.model.get( 'user' ).email ) ){ return; } - - // annotation slide down - var historyAnnotationArea = this.$( '#history-annotation-area' ); - this.$( '#history-annotate' ).click( function() { - if ( historyAnnotationArea.is( ":hidden" ) ) { - historyAnnotationArea.slideDown( "fast" ); - } else { - historyAnnotationArea.slideUp( "fast" ); - } - return false; - }); - - // title and annotation editable text - //NOTE: these use page scoped selectors - so these need to be in the page DOM before they're applicable - async_save_text( "history-name-container", "history-name", - this.urls.rename, "new_name", 18 ); - - async_save_text( "history-annotation-container", "history-annotation", - this.urls.annotate, "new_annotation", 18, true, 4 ); - }, - - // update the history size display (curr. upper right of panel) - updateHistoryDiskSize : function(){ - this.$el.find( '#history-size' ).text( this.model.get( 'nice_size' ) ); - }, - - //TODO: this seems more like a per user message than a history message; IOW, this doesn't belong here - showQuotaMessage : function( userData ){ - var msg = this.$el.find( '#quota-message-container' ); - //this.log( this + ' showing quota message:', msg, userData ); - if( msg.is( ':hidden' ) ){ msg.slideDown( 'fast' ); } - }, - - //TODO: this seems more like a per user message than a history message - hideQuotaMessage : function( userData ){ - var msg = this.$el.find( '#quota-message-container' ); - //this.log( this + ' hiding quota message:', msg, userData ); - if( !msg.is( ':hidden' ) ){ msg.slideUp( 'fast' ); } - }, - - events : { - 'click #history-collapse-all' : 'hideAllHdaBodies', - 'click #history-tag' : 'loadAndDisplayTags' - }, - - // collapse all hda bodies - hideAllHdaBodies : function(){ - _.each( this.hdaViews, function( item ){ - item.toggleBodyVisibility( null, false ); - }); - this.storage.set( 'expandedHdas', {} ); - }, - - // find the tag area and, if initial: (via ajax) load the html for displaying them; otherwise, unhide/hide - //TODO: into sub-MV - loadAndDisplayTags : function( event ){ - this.log( this + '.loadAndDisplayTags', event ); - var tagArea = this.$el.find( '#history-tag-area' ), - tagElt = tagArea.find( '.tag-elt' ); - this.log( '\t tagArea', tagArea, ' tagElt', tagElt ); - - // Show or hide tag area; if showing tag area and it's empty, fill it - if( tagArea.is( ":hidden" ) ){ - if( !jQuery.trim( tagElt.html() ) ){ - var view = this; - // Need to fill tag element. - $.ajax({ - //TODO: the html from this breaks a couple of times - url: view.urls.tag, - error: function() { alert( "Tagging failed" ); }, - success: function(tag_elt_html) { - //view.log( view + ' tag elt html (ajax)', tag_elt_html ); - tagElt.html(tag_elt_html); - tagElt.find(".tooltip").tooltip(); - tagArea.slideDown("fast"); - } - }); - } else { - // Tag element already filled: show - tagArea.slideDown("fast"); - } - - } else { - // Currently shown: Hide - tagArea.slideUp("fast"); - } - return false; - }, - - toString : function(){ - var nameString = this.model.get( 'name' ) || ''; - return 'HistoryView(' + nameString + ')'; - } -}); -HistoryView.templates = { - historyPanel : Handlebars.templates[ 'template-history-historyPanel' ] -}; - //============================================================================== //return { -// HistoryItem : HistoryItem, // HDAView : HDAView, -// HistoryCollection : HistoryCollection, -// History : History, -// HistoryView : HistoryView //};}); diff --git a/static/scripts/mvc/dataset/hda-model.js b/static/scripts/mvc/dataset/hda-model.js new file mode 100644 index 00000000000..91baa1c2071 --- /dev/null +++ b/static/scripts/mvc/dataset/hda-model.js @@ -0,0 +1,223 @@ +//define([ +// "../mvc/base-mvc" +//], function(){ +//============================================================================== +/** + * + */ +var HistoryDatasetAssociation = BaseModel.extend( LoggableMixin ).extend({ + // a single HDA model + + // uncomment this out see log messages + //logger : console, + + defaults : { + // ---these are part of an HDA proper: + + // parent (containing) history + history_id : null, + // often used with tagging + model_class : 'HistoryDatasetAssociation', + // index within history (??) + hid : 0, + + // ---whereas these are Dataset related/inherited + + id : null, + name : '', + // one of HistoryDatasetAssociation.STATES + state : '', + // sniffed datatype (sam, tabular, bed, etc.) + data_type : null, + // size in bytes + file_size : 0, + + // array of associated file types (eg. [ 'bam_index', ... ]) + meta_files : [], + + misc_blurb : '', + misc_info : '', + + deleted : false, + purged : false, + // aka. !hidden + visible : false, + // based on trans.user (is_admin or security_agent.can_access_dataset( , hda.dataset )) + accessible : false, + + //TODO: this needs to be removed (it is a function of the view type (e.g. HDAForEditingView)) + for_editing : true + }, + + // fetch location of this history in the api + url : function(){ + //TODO: get this via url router + return 'api/histories/' + this.get( 'history_id' ) + '/contents/' + this.get( 'id' ); + }, + + // (curr) only handles changing state of non-accessible hdas to STATES.NOT_VIEWABLE + //TODO:? use initialize (or validate) to check purged AND deleted -> purged XOR deleted + initialize : function(){ + this.log( this + '.initialize', this.attributes ); + this.log( '\tparent history_id: ' + this.get( 'history_id' ) ); + + //!! this state is not in trans.app.model.Dataset.states - set it here - + //TODO: change to server side. + if( !this.get( 'accessible' ) ){ + this.set( 'state', HistoryDatasetAssociation.STATES.NOT_VIEWABLE ); + } + + // if the state has changed and the new state is a ready state, fire an event + this.on( 'change:state', function( currModel, newState ){ + this.log( this + ' has changed state:', currModel, newState ); + if( this.inReadyState() ){ + this.trigger( 'state:ready', this.get( 'id' ), newState, this.previous( 'state' ), currModel ); + } + }); + + // debug on change events + //this.on( 'change', function( currModel, changedList ){ + // this.log( this + ' has changed:', currModel, changedList ); + //}); + //this.bind( 'all', function( event ){ + // this.log( this + '', arguments ); + //}); + }, + + isDeletedOrPurged : function(){ + return ( this.get( 'deleted' ) || this.get( 'purged' ) ); + }, + + // based on show_deleted, show_hidden (gen. from the container control), would this ds show in the list of ds's? + //TODO: too many visibles + isVisible : function( show_deleted, show_hidden ){ + var isVisible = true; + if( ( !show_deleted ) + && ( this.get( 'deleted' ) || this.get( 'purged' ) ) ){ + isVisible = false; + } + if( ( !show_hidden ) + && ( !this.get( 'visible' ) ) ){ + isVisible = false; + } + return isVisible; + }, + + // 'ready' states are states where no processing (for the ds) is left to do on the server + inReadyState : function(){ + var state = this.get( 'state' ); + return ( + ( state === HistoryDatasetAssociation.STATES.NEW ) + || ( state === HistoryDatasetAssociation.STATES.OK ) + || ( state === HistoryDatasetAssociation.STATES.EMPTY ) + || ( state === HistoryDatasetAssociation.STATES.FAILED_METADATA ) + || ( state === HistoryDatasetAssociation.STATES.NOT_VIEWABLE ) + || ( state === HistoryDatasetAssociation.STATES.DISCARDED ) + || ( state === HistoryDatasetAssociation.STATES.ERROR ) + ); + }, + + // convenience fn to match hda.has_data + hasData : function(){ + //TODO:?? is this equivalent to all possible hda.has_data calls? + return ( this.get( 'file_size' ) > 0 ); + }, + + toString : function(){ + var nameAndId = this.get( 'id' ) || ''; + if( this.get( 'name' ) ){ + nameAndId += ':"' + this.get( 'name' ) + '"'; + } + return 'HistoryDatasetAssociation(' + nameAndId + ')'; + } +}); + +//------------------------------------------------------------------------------ +HistoryDatasetAssociation.STATES = { + UPLOAD : 'upload', + QUEUED : 'queued', + RUNNING : 'running', + SETTING_METADATA : 'setting_metadata', + + NEW : 'new', + OK : 'ok', + EMPTY : 'empty', + + FAILED_METADATA : 'failed_metadata', + NOT_VIEWABLE : 'noPermission', // not in trans.app.model.Dataset.states + DISCARDED : 'discarded', + ERROR : 'error' +}; + +//============================================================================== +/** + * + */ +var HDACollection = Backbone.Collection.extend( LoggableMixin ).extend({ + model : HistoryDatasetAssociation, + + //logger : console, + + initialize : function(){ + //this.bind( 'all', function( event ){ + // this.log( this + '', arguments ); + //}); + }, + + // return the ids of every hda in this collection + ids : function(){ + return this.map( function( item ){ return item.id; }); + }, + + // return an HDA collection containing every 'shown' hda based on show_deleted/hidden + getVisible : function( show_deleted, show_hidden ){ + return this.filter( function( item ){ return item.isVisible( show_deleted, show_hidden ); }); + }, + + // get a map where : [ ] + getStateLists : function(){ + var stateLists = {}; + _.each( _.values( HistoryDatasetAssociation.STATES ), function( state ){ + stateLists[ state ] = []; + }); + //NOTE: will err on unknown state + this.each( function( item ){ + stateLists[ item.get( 'state' ) ].push( item.get( 'id' ) ); + }); + return stateLists; + }, + + // returns the id of every hda still running (not in a ready state) + running : function(){ + var idList = []; + this.each( function( item ){ + if( !item.inReadyState() ){ + idList.push( item.get( 'id' ) ); + } + }); + return idList; + }, + + // update (fetch -> render) the hdas with the ids given + update : function( ids ){ + this.log( this + 'update:', ids ); + + if( !( ids && ids.length ) ){ return; } + + var collection = this; + _.each( ids, function( id, index ){ + var historyItem = collection.get( id ); + historyItem.fetch(); + }); + }, + + toString : function(){ + return ( 'HDACollection(' + this.ids().join(',') + ')' ); + } +}); + +//============================================================================== +//return { +// HistoryDatasetAssociation : HistoryDatasetAssociation, +// HDACollection : HDACollection, +//};}); diff --git a/static/scripts/mvc/history/history-model.js b/static/scripts/mvc/history/history-model.js new file mode 100644 index 00000000000..0335ae570a9 --- /dev/null +++ b/static/scripts/mvc/history/history-model.js @@ -0,0 +1,180 @@ +//define([ +// "../mvc/base-mvc" +//], function(){ +//============================================================================== +/** + * + */ +var History = BaseModel.extend( LoggableMixin ).extend({ + //TODO: bind change events from items and collection to this (itemLengths, states) + + // uncomment this out see log messages + //logger : console, + + // values from api (may need more) + defaults : { + id : '', + name : '', + state : '', + + //TODO: wire these to items (or this) + show_deleted : false, + show_hidden : false, + + diskSize : 0, + deleted : false, + + tags : [], + annotation : null, + + //TODO: quota msg and message? how to get those over the api? + message : null, + quotaMsg : false + }, + + url : function(){ + // api location of history resource + //TODO: hardcoded + return 'api/histories/' + this.get( 'id' ); + }, + + initialize : function( initialSettings, initialHdas ){ + this.log( this + ".initialize:", initialSettings, initialHdas ); + + this.hdas = new HDACollection(); + + // if we've got hdas passed in the constructor, load them and set up updates if needed + if( initialHdas && initialHdas.length ){ + this.hdas.reset( initialHdas ); + this.checkForUpdates(); + } + + //this.on( 'change', function( currModel, changedList ){ + // this.log( this + ' has changed:', currModel, changedList ); + //}); + //this.bind( 'all', function( event ){ + // this.log( this + '', arguments ); + //}); + }, + + // get data via the api (alternative to sending options,hdas to initialize) + loadFromApi : function( historyId, callback ){ + var history = this; + + // fetch the history AND the user (mainly to see if they're logged in at this point) + history.attributes.id = historyId; + //TODO:?? really? fetch user here? + jQuery.when( jQuery.ajax( 'api/users/current' ), history.fetch() + + ).then( function( userResponse, historyResponse ){ + //console.warn( 'fetched user, history: ', userResponse, historyResponse ); + history.attributes.user = userResponse[0]; //? meh. + history.log( history ); + + }).then( function(){ + // ...then the hdas (using contents?ids=...) + jQuery.ajax( history.url() + '/contents?' + jQuery.param({ + ids : history.itemIdsFromStateIds().join( ',' ) + + // reset the collection to the hdas returned + })).success( function( hdas ){ + //console.warn( 'fetched hdas' ); + history.hdas.reset( hdas ); + history.checkForUpdates(); + callback(); + }); + }); + }, + + // reduce the state_ids map of hda id lists -> a single list of ids + //...ugh - seems roundabout; necessary because the history doesn't have a straightforward list of ids + // (and history_contents/index curr returns a summary only) + hdaIdsFromStateIds : function(){ + return _.reduce( _.values( this.get( 'state_ids' ) ), function( reduction, currIdList ){ + return reduction.concat( currIdList ); + }); + }, + + // get the history's state from it's cummulative ds states, delay + update if needed + checkForUpdates : function( datasets ){ + // get overall History state from collection, run updater if History has running/queued hdas + // boiling it down on the client to running/not + if( this.hdas.running().length ){ + this.stateUpdater(); + } + return this; + }, + + // update this history, find any hda's running/queued, update ONLY those that have changed states, + // set up to run this again in some interval of time + stateUpdater : function(){ + var history = this, + oldState = this.get( 'state' ), + // state ids is a map of every possible hda state, each containing a list of ids for hdas in that state + oldStateIds = this.get( 'state_ids' ); + + // pull from the history api + //TODO: fetch? + jQuery.ajax( 'api/histories/' + this.get( 'id' ) + + ).success( function( response ){ + //this.log( 'historyApiRequest, response:', response ); + history.set( response ); + history.log( 'current history state:', history.get( 'state' ), + '(was)', oldState, + 'new size:', history.get( 'nice_size' ) ); + + //TODO: revisit this - seems too elaborate, need something straightforward + // for each state, check for the difference between old dataset states and new + // the goal here is to check ONLY those datasets that have changed states (not all datasets) + var changedIds = []; + _.each( _.keys( response.state_ids ), function( state ){ + var diffIds = _.difference( response.state_ids[ state ], oldStateIds[ state ] ); + // aggregate those changed ids + changedIds = changedIds.concat( diffIds ); + }); + + // send the changed ids (if any) to dataset collection to have them fetch their own model changes + if( changedIds.length ){ + history.hdas.update( changedIds ); + } + + // set up to keep pulling if this history in run/queue state + //TODO: magic number here + if( ( history.get( 'state' ) === HistoryDatasetAssociation.STATES.RUNNING ) + || ( history.get( 'state' ) === HistoryDatasetAssociation.STATES.QUEUED ) ){ + setTimeout( function(){ + history.stateUpdater(); + }, 4000 ); + } + + }).error( function( xhr, status, error ){ + if( console && console.warn ){ + console.warn( 'Error getting history updates from the server:', xhr, status, error ); + } + alert( 'Error getting history updates from the server.\n' + error ); + }); + }, + + toString : function(){ + var nameString = ( this.get( 'name' ) )? + ( ',' + this.get( 'name' ) ) : ( '' ); + return 'History(' + this.get( 'id' ) + nameString + ')'; + } +}); + +//============================================================================== +/** A collection of histories (per user or admin) + * (stub) currently unused + */ +var HistoryCollection = Backbone.Collection.extend( LoggableMixin ).extend({ + model : History, + urlRoot : 'api/histories', + logger : console +}); + +//============================================================================== +//return { +// History : History, +// HistoryCollection : HistoryCollection, +//};}); diff --git a/static/scripts/mvc/history/history-panel.js b/static/scripts/mvc/history/history-panel.js new file mode 100644 index 00000000000..5bad8c52794 --- /dev/null +++ b/static/scripts/mvc/history/history-panel.js @@ -0,0 +1,380 @@ +//define([ +// "../mvc/base-mvc" +//], function(){ +/* ============================================================================= +Backbone.js implementation of history panel + +TODO: + anon user, mako template init: + bug: rename url seems to be wrong url + + logged in, mako template: + BUG: meter is not updating RELIABLY on change:nice_size + BUG: am able to start upload even if over quota - 'runs' forever + bug: quotaMeter bar rendering square in chrome + BUG: quotaMsg not showing when 100% (on load) + BUG: imported, shared history with unaccessible dataset errs in historycontents when getting history + (entire history is inaccessible) + ??: still happening? + + from loadFromApi: + BUG: not showing previous annotations + + fixed: + BUG: upload, history size, doesn't change + FIXED: using change:nice_size to trigger re-render of history size + BUG: delete uploading hda - now in state 'discarded'! ...new state to handle + FIXED: handled state + BUG: historyItem, error'd ds show display, download? + FIXED: removed + bug: loading hdas (alt_hist) + FIXED: added anon user api request ( trans.user == None and trans.history.id == requested id ) + bug: quota meter not updating on upload/tool run + FIXED: quotaMeter now listens for 'state:ready' from glx_history in alternate_history.mako + bug: use of new HDACollection with event listener in init doesn't die...keeps reporting + FIXED: change getVisible to return an array + BUG: history, broken intial hist state (running, updater, etc.) + ??: doesn't seem to happen anymore + BUG: collapse all should remove all expanded from storage + FIXED: hideAllItemBodies now resets storage.expandedItems + BUG: historyItem, shouldn't allow tag, annotate, peek on purged datasets + FIXED: ok state now shows only: info, rerun + BUG: history?, some ids aren't returning encoded... + FIXED:??? + BUG: history, showing deleted ds + FIXED + UGH: historyItems have to be decorated with history_ids (api/histories/:history_id/contents/:id) + FIXED by adding history_id to history_contents.show + BUG: history, if hist has err'd ds, hist has perm state 'error', updater on following ds's doesn't run + FIXED by reordering history state from ds' states here and histories + BUG: history, broken annotation on reload (can't get thru api (sets fine, tho)) + FIXED: get thru api for now + + replication: + show_deleted/hidden: + use storage + on/off ui + move histview fadein/out in render to app? + don't draw body until it's first expand event + localize all + ?: render url templates on init or render? + ?: history, annotation won't accept unicode + + RESTful: + move over webui functions available in api + delete, undelete + update? + currently, adding a dataset (via tool execute, etc.) creates a new dataset and refreshes the page + provide a means to update the panel via js + + hierarchy: + to relational model? + HDACollection, meta_files, display_apps, etc. + dataset -> hda + history -> historyForEditing, historyForViewing + display_structured? + + meta: + css/html class/id 'item' -> hda + add classes, ids on empty divs + events (local/ui and otherwise) + list in docs as well + require.js + convert function comments to jsDoc style, complete comments + move inline styles into base.less + watch the magic strings + watch your globals + + feature creep: + lineage + hide button + show permissions in info + show shared/sharing status on ds, history + maintain scroll position on refresh (storage?) + selection, multi-select (and actions common to selected (ugh)) + searching + sorting, re-shuffling + +============================================================================= */ +/** view for the HDACollection (as per current right hand panel) + * + */ +var HistoryPanel = BaseView.extend( LoggableMixin ).extend({ + + // uncomment this out see log messages + //logger : console, + + // direct attachment to existing element + el : 'body.historyPage', + + // init with the model, urlTemplates, set up storage, bind HDACollection events + //NOTE: this will create or load PersistantStorage keyed under 'HistoryView.' + //pre: you'll need to pass in the urlTemplates (urlTemplates : { history : {...}, hda : {...} }) + initialize : function( attributes ){ + this.log( this + '.initialize:', attributes ); + + // set up url templates + //TODO: prob. better to put this in class scope (as the handlebars templates), but... + // they're added to GalaxyPaths on page load (after this file is loaded) + if( !attributes.urlTemplates ){ throw( 'HDAView needs urlTemplates on initialize' ); } + if( !attributes.urlTemplates.history ){ throw( 'HDAView needs urlTemplates.history on initialize' ); } + if( !attributes.urlTemplates.hda ){ throw( 'HDAView needs urlTemplates.hda on initialize' ); } + this.urlTemplates = attributes.urlTemplates.history; + this.hdaUrlTemplates = attributes.urlTemplates.hda; + + // data that needs to be persistant over page refreshes + // (note the key function which uses the history id as well) + this.storage = new PersistantStorage( + 'HistoryView.' + this.model.get( 'id' ), + { expandedHdas : {} } + ); + + // bind events from the model's hda collection + //this.model.bind( 'change', this.render, this ); + this.model.bind( 'change:nice_size', this.updateHistoryDiskSize, this ); + + this.model.hdas.bind( 'add', this.add, this ); + this.model.hdas.bind( 'reset', this.addAll, this ); + this.model.hdas.bind( 'all', this.all, this ); + + //this.bind( 'all', function(){ + // this.log( arguments ); + //}, this ); + + // set up instance vars + this.hdaViews = {}; + this.urls = {}; + }, + + add : function( hda ){ + //console.debug( 'add.' + this, hda ); + //TODO + }, + + addAll : function(){ + //console.debug( 'addAll.' + this ); + // re render when all hdas are reset + this.render(); + }, + + all : function( event ){ + //console.debug( 'allItemEvents.' + this, event ); + //...for which to do the debuggings + }, + + // render the urls for this view using urlTemplates and the model data + renderUrls : function( modelJson ){ + var historyView = this; + + historyView.urls = {}; + _.each( this.urlTemplates, function( urlTemplate, urlKey ){ + historyView.urls[ urlKey ] = _.template( urlTemplate, modelJson ); + }); + return historyView.urls; + }, + + // render urls, historyView body, and hdas (if any are shown), fade out, swap, fade in, set up behaviours + render : function(){ + var historyView = this, + setUpQueueName = historyView.toString() + '.set-up', + newRender = $( '
' ), + modelJson = this.model.toJSON(), + initialRender = ( this.$el.children().size() === 0 ); + + //console.debug( this + '.render, initialRender:', initialRender ); + + // render the urls and add them to the model json + modelJson.urls = this.renderUrls( modelJson ); + + // render the main template, tooltips + //NOTE: this is done before the items, since item views should handle theirs themselves + newRender.append( HistoryPanel.templates.historyPanel( modelJson ) ); + newRender.find( '.tooltip' ).tooltip(); + + // render hda views (if any and any shown (show_deleted/hidden) + if( !this.model.hdas.length + || !this.renderItems( newRender.find( '#' + this.model.get( 'id' ) + '-datasets' ) ) ){ + // if history is empty or no hdas would be rendered, show the empty message + newRender.find( '#emptyHistoryMessage' ).show(); + } + + // fade out existing, swap with the new, fade in, set up behaviours + $( historyView ).queue( setUpQueueName, function( next ){ + historyView.$el.fadeOut( 'fast', function(){ next(); }); + }); + $( historyView ).queue( setUpQueueName, function( next ){ + // swap over from temp div newRender + historyView.$el.html( '' ); + historyView.$el.append( newRender.children() ); + + historyView.$el.fadeIn( 'fast', function(){ next(); }); + }); + $( historyView ).queue( setUpQueueName, function( next ){ + this.log( historyView + ' rendered:', historyView.$el ); + + //TODO: ideally, these would be set up before the fade in (can't because of async save text) + historyView.setUpBehaviours(); + + if( initialRender ){ + historyView.trigger( 'rendered:initial' ); + + } else { + historyView.trigger( 'rendered' ); + } + next(); + }); + $( historyView ).dequeue( setUpQueueName ); + return this; + }, + + // set up a view for each item to be shown, init with model and listeners, cache to map ( model.id : view ) + renderItems : function( $whereTo ){ + this.hdaViews = {}; + var historyView = this, + show_deleted = this.model.get( 'show_deleted' ), + show_hidden = this.model.get( 'show_hidden' ), + visibleHdas = this.model.hdas.getVisible( show_deleted, show_hidden ); + + // only render the shown hdas + _.each( visibleHdas, function( hda ){ + var hdaId = hda.get( 'id' ), + expanded = historyView.storage.get( 'expandedHdas' ).get( hdaId ); + historyView.hdaViews[ hdaId ] = new HDAView({ + model : hda, + expanded : expanded, + urlTemplates : historyView.hdaUrlTemplates + }); + historyView.setUpHdaListeners( historyView.hdaViews[ hdaId ] ); + // render it (NOTE: reverse order, newest on top (prepend)) + //TODO: by default send a reverse order list (although this may be more efficient - it's more confusing) + $whereTo.prepend( historyView.hdaViews[ hdaId ].render().$el ); + }); + return visibleHdas.length; + }, + + // set up HistoryView->HDAView listeners + setUpHdaListeners : function( hdaView ){ + var historyView = this; + + // use storage to maintain a list of hdas whose bodies are expanded + hdaView.bind( 'toggleBodyVisibility', function( id, visible ){ + if( visible ){ + historyView.storage.get( 'expandedHdas' ).set( id, true ); + } else { + historyView.storage.get( 'expandedHdas' ).deleteKey( id ); + } + }); + + // rendering listeners + hdaView.bind( 'rendered:ready', function(){ historyView.trigger( 'hda:rendered:ready' ); }); + }, + + // set up js/widget behaviours: tooltips, + //TODO: these should be either sub-MVs, or handled by events + setUpBehaviours : function(){ + // anon users shouldn't have access to any of these + if( !( this.model.get( 'user' ) && this.model.get( 'user' ).email ) ){ return; } + + // annotation slide down + var historyAnnotationArea = this.$( '#history-annotation-area' ); + this.$( '#history-annotate' ).click( function() { + if ( historyAnnotationArea.is( ":hidden" ) ) { + historyAnnotationArea.slideDown( "fast" ); + } else { + historyAnnotationArea.slideUp( "fast" ); + } + return false; + }); + + // title and annotation editable text + //NOTE: these use page scoped selectors - so these need to be in the page DOM before they're applicable + async_save_text( "history-name-container", "history-name", + this.urls.rename, "new_name", 18 ); + + async_save_text( "history-annotation-container", "history-annotation", + this.urls.annotate, "new_annotation", 18, true, 4 ); + }, + + // update the history size display (curr. upper right of panel) + updateHistoryDiskSize : function(){ + this.$el.find( '#history-size' ).text( this.model.get( 'nice_size' ) ); + }, + + //TODO: this seems more like a per user message than a history message; IOW, this doesn't belong here + showQuotaMessage : function( userData ){ + var msg = this.$el.find( '#quota-message-container' ); + //this.log( this + ' showing quota message:', msg, userData ); + if( msg.is( ':hidden' ) ){ msg.slideDown( 'fast' ); } + }, + + //TODO: this seems more like a per user message than a history message + hideQuotaMessage : function( userData ){ + var msg = this.$el.find( '#quota-message-container' ); + //this.log( this + ' hiding quota message:', msg, userData ); + if( !msg.is( ':hidden' ) ){ msg.slideUp( 'fast' ); } + }, + + events : { + 'click #history-collapse-all' : 'hideAllHdaBodies', + 'click #history-tag' : 'loadAndDisplayTags' + }, + + // collapse all hda bodies + hideAllHdaBodies : function(){ + _.each( this.hdaViews, function( item ){ + item.toggleBodyVisibility( null, false ); + }); + this.storage.set( 'expandedHdas', {} ); + }, + + // find the tag area and, if initial: (via ajax) load the html for displaying them; otherwise, unhide/hide + //TODO: into sub-MV + loadAndDisplayTags : function( event ){ + this.log( this + '.loadAndDisplayTags', event ); + var tagArea = this.$el.find( '#history-tag-area' ), + tagElt = tagArea.find( '.tag-elt' ); + this.log( '\t tagArea', tagArea, ' tagElt', tagElt ); + + // Show or hide tag area; if showing tag area and it's empty, fill it + if( tagArea.is( ":hidden" ) ){ + if( !jQuery.trim( tagElt.html() ) ){ + var view = this; + // Need to fill tag element. + $.ajax({ + //TODO: the html from this breaks a couple of times + url: view.urls.tag, + error: function() { alert( "Tagging failed" ); }, + success: function(tag_elt_html) { + //view.log( view + ' tag elt html (ajax)', tag_elt_html ); + tagElt.html(tag_elt_html); + tagElt.find(".tooltip").tooltip(); + tagArea.slideDown("fast"); + } + }); + } else { + // Tag element already filled: show + tagArea.slideDown("fast"); + } + + } else { + // Currently shown: Hide + tagArea.slideUp("fast"); + } + return false; + }, + + toString : function(){ + var nameString = this.model.get( 'name' ) || ''; + return 'HistoryView(' + nameString + ')'; + } +}); + +//------------------------------------------------------------------------------ +HistoryPanel.templates = { + historyPanel : Handlebars.templates[ 'template-history-historyPanel' ] +}; + +//============================================================================== +//return { +// HistoryPanel : HistoryPanel +//};}); diff --git a/templates/root/alternate_history.mako b/templates/root/alternate_history.mako index 1482dfdb575..f19545d425e 100644 --- a/templates/root/alternate_history.mako +++ b/templates/root/alternate_history.mako @@ -222,97 +222,127 @@ ${h.templates( "template-user-quotaMeter-usage" )} -##TODO: fix: curr hasta be _after_ h.templates - move somehow +##TODO: fix: curr hasta be _after_ h.templates bc these use those templates - move somehow ${h.js( - "mvc/history", + "mvc/dataset/hda-model", "mvc/dataset/hda-edit", + "mvc/history/history-model", "mvc/history/history-panel", + ##"mvc/tags", "mvc/annotations", "mvc/user/user-model", "mvc/user/user-quotameter" )}