UNPKG

@gmod/jbrowse

Version:

JBrowse - client-side genome browser

249 lines (223 loc) 11.2 kB
/** * Mixin with methods for parsing making default feature detail dialogs. */ define([ 'dojo/_base/declare', 'dojo/_base/array', 'dojo/_base/lang', 'dojo/aspect', 'dojo/on', 'dojo/dom-construct', 'JBrowse/Util', 'JBrowse/View/FASTA', 'JBrowse/View/_FeatureDescriptionMixin' ], function( declare, array, lang, aspect, on, domConstruct, Util, FASTAView, FeatureDescriptionMixin ) { return declare( FeatureDescriptionMixin, { constructor: function() { // clean up the eventHandlers at destruction time if possible if( typeof this.destroy == 'function' ) { aspect.before( this, 'destroy', function() { delete this.eventHandlers; }); } }, _setupEventHandlers: function() { // make a default click event handler var eventConf = dojo.clone( this.config.events || {} ); if( ! eventConf.click ) { eventConf.click = (this.config.style||{}).linkTemplate ? { action: "newWindow", url: this.config.style.linkTemplate } : { action: "contentDialog", title: '{type} {name}', content: dojo.hitch( this, 'defaultFeatureDetail' ) }; } // process the configuration to set up our event handlers this.eventHandlers = (function() { var handlers = dojo.clone( eventConf ); // find conf vars that set events, like `onClick` for( var key in this.config ) { var handlerName = key.replace(/^on(?=[A-Z])/, ''); if( handlerName != key ) handlers[ handlerName.toLowerCase() ] = this.config[key]; } // interpret handlers that are just strings to be URLs that should be opened for( key in handlers ) { if( typeof handlers[key] == 'string' ) handlers[key] = { url: handlers[key] }; } return handlers; }).call(this); this.eventHandlers.click = this._makeClickHandler( this.eventHandlers.click ); }, /** * Make a default feature detail page for the given feature. * @returns {HTMLElement} feature detail page HTML */ defaultFeatureDetail: function( /** JBrowse.Track */ track, /** Object */ f, /** HTMLElement */ featDiv, /** HTMLElement */ container, layer ) { container = container || dojo.create('div', { className: 'detail feature-detail feature-detail-'+track.name.replace(/\s+/g,'_').toLowerCase(), innerHTML: '' } ); this._renderCoreDetails( track, f, featDiv, container ); this._renderAdditionalTagsDetail( track, f, featDiv, container ); if (!this.config.hideSequenceBox) { this._renderUnderlyingReferenceSequence( track, f, featDiv, container ); } this._renderSubfeaturesDetail( track, f, featDiv, container, layer||1 ); // hook function extendedRender(track,f,featDiv,container) if (typeof this.extendedRender === 'function') { this.extendedRender(track,f,featDiv,container); } return container; }, _renderCoreDetails: function( track, f, featDiv, container ) { var coreDetails = dojo.create('div', { className: 'core' }, container ); var fmt = dojo.hitch( this, 'renderDetailField', coreDetails ); coreDetails.innerHTML += '<h2 class="sectiontitle">Primary Data</h2>'; fmt( 'Name', this.getFeatureLabel( f ), f ); fmt( 'Type', f.get('type'),f ); fmt( 'Score', f.get('score'),f ); fmt( 'Description', this.getFeatureDescription( f ),f ); fmt( 'Position', Util.assembleLocString({ start: f.get('start'), end: f.get('end'), ref: this.refSeq.name, strand: f.get('strand') }),f ); fmt( 'Length', Util.addCommas(f.get('end')-f.get('start'))+' bp',f ); }, // render any subfeatures this feature has _renderSubfeaturesDetail: function( track, f, featDiv, container, layer ) { var thisB = this; var subfeatures = f.get('subfeatures'); if( subfeatures && subfeatures.length ) { if( f.get('strand') == -1 ) { // Feature is on the oposite strand, lets reverse the order of the subfeatures according to their start position subfeatures.sort(function( a, b ) { return b.get('start')-a.get('start') }); } if( !(track.config.subfeatureDetailLevel != null) || layer < track.config.subfeatureDetailLevel ) { this._subfeaturesDetail( track, subfeatures, container, f, layer + 1 ); } else if(layer >= track.config.subfeatureDetailLevel) { var b = domConstruct.create('button', { className: 'subfeature-load-button', innerHTML: 'Show subfeatures...' }, container); on(b, 'click', function() { thisB._subfeaturesDetail( track, subfeatures, container, f, layer + 1 ); dojo.destroy(b); }); } } }, _isReservedTag: function( t ) { return {name:1,start:1,end:1,strand:1,note:1,subfeatures:1,type:1,score:1}[t.toLowerCase()]; }, // render any additional tags as just key/value _renderAdditionalTagsDetail: function( track, f, featDiv, container ) { var thisB = this; var additionalTags = array.filter( f.tags(), function(t) { if(thisB.config.showNoteInAttributes && t.toLowerCase()=='note'){ return true ; } return ! this._isReservedTag( t ); },this); if( additionalTags.length ) { var atElement = domConstruct.create( 'div', { className: 'additional', innerHTML: '<h2 class="sectiontitle">Attributes</h2>' }, container ) additionalTags.sort().forEach( t => { this.renderDetailField( atElement, t, f.get(t), f, undefined, track.store.getTagMetadata(t)) }) } }, _renderUnderlyingReferenceSequence: function( track, f, featDiv, container ) { // render the sequence underlying this feature if possible var field_container = dojo.create('div', { className: 'field_container feature_sequence' }, container ); dojo.create( 'h2', { className: 'field feature_sequence', innerHTML: 'Region sequence', title: 'reference sequence underlying this '+(f.get('type') || 'feature') }, field_container ); var valueContainerID = 'feature_sequence'+this._uniqID(); var valueContainer = dojo.create( 'div', { id: valueContainerID, innerHTML: '<div style="height: 12em">Loading...</div>', className: 'value feature_sequence' }, field_container); var maxSize = this.config.maxFeatureSizeForUnderlyingRefSeq; if( maxSize < (f.get('end') - f.get('start')) ) { valueContainer.innerHTML = 'Not displaying underlying reference sequence, feature is longer than maximum of '+Util.humanReadableNumber(maxSize)+'bp'; } else { track.browser.getStore('refseqs', dojo.hitch(this,function( refSeqStore ) { valueContainer = dojo.byId(valueContainerID) || valueContainer; if( refSeqStore ) { refSeqStore.getReferenceSequence( { ref: this.refSeq.name, start: f.get('start'), end: f.get('end')}, // feature callback dojo.hitch( this, function( seq ) { valueContainer = dojo.byId(valueContainerID) || valueContainer; valueContainer.innerHTML = ''; // the HTML is rewritten by the dojo dialog // parser, but this callback may be called either // before or after that happens. if the fetch by // ID fails, we have come back before the parse. var textArea = new FASTAView({ track: this, width: 62, htmlMaxRows: 10 }) .renderHTML( { ref: this.refSeq.name, start: f.get('start'), end: f.get('end'), strand: f.get('strand'), type: f.get('type') }, f.get('strand') == -1 ? Util.revcom(seq) : seq, valueContainer ); }), // end callback function() {}, // error callback dojo.hitch( this, function() { valueContainer = dojo.byId(valueContainerID) || valueContainer; valueContainer.innerHTML = '<span class="ghosted">reference sequence not available</span>'; }) ); } else { valueContainer.innerHTML = '<span class="ghosted">reference sequence not available</span>'; } })); } }, _uniqID: function() { this._idCounter = this._idCounter || 0; return this._idCounter++; }, _subfeaturesDetail: function( track, subfeatures, container, f, layer ) { var field_container = dojo.create('div', { className: 'field_container subfeatures' }, container ); dojo.create( 'h2', { className: 'field subfeatures', innerHTML: 'Subfeatures' }, field_container ); var subfeaturesContainer = dojo.create( 'div', { className: 'value subfeatures' }, field_container ); array.forEach( subfeatures || [], function( subfeature ) { this.defaultFeatureDetail( track, subfeature, null, dojo.create('div', { className: 'detail feature-detail subfeature-detail feature-detail-'+track.name+' subfeature-detail-'+track.name, innerHTML: '' }, subfeaturesContainer ), layer ); },this); } }); });