public/js/report.js in sequenceserver-2.0.0.beta3 vs public/js/report.js in sequenceserver-2.0.0.beta4
- old
+ new
@@ -1,6 +1,6 @@
-import './sequenceserver' // for custom $.tooltip function
+import './sequenceserver'; // for custom $.tooltip function
import React from 'react';
import _ from 'underscore';
import Circos from './circos';
import HitsOverview from './hits_overview';
@@ -17,20 +17,20 @@
/**
* Dynamically create form and submit.
*/
var downloadFASTA = function (sequence_ids, database_ids) {
var form = $('<form/>').attr('method', 'post').attr('action', 'get_sequence');
- addField("sequence_ids", sequence_ids);
- addField("database_ids", database_ids);
+ addField('sequence_ids', sequence_ids);
+ addField('database_ids', database_ids);
form.appendTo('body').submit().remove();
function addField(name, val) {
form.append(
$('<input>').attr('type', 'hidden').attr('name', name).val(val)
);
}
-}
+};
/**
* Base component of report page. This component is later rendered into page's
* '#view' element.
*/
@@ -84,28 +84,28 @@
function poll () {
$.getJSON(location.pathname + '.json')
.complete(function (jqXHR) {
switch (jqXHR.status) {
- case 202:
- var interval;
- if (intervals.length === 1) {
- interval = intervals[0];
- }
- else {
- interval = intervals.shift();
- }
- setTimeout(poll, interval);
- break;
- case 200:
- component.updateState(jqXHR.responseJSON);
- break;
- case 404:
- case 400:
- case 500:
- showErrorModal(jqXHR.responseJSON);
- break;
+ case 202:
+ var interval;
+ if (intervals.length === 1) {
+ interval = intervals[0];
+ }
+ else {
+ interval = intervals.shift();
+ }
+ setTimeout(poll, interval);
+ break;
+ case 200:
+ component.updateState(jqXHR.responseJSON);
+ break;
+ case 404:
+ case 400:
+ case 500:
+ showErrorModal(jqXHR.responseJSON);
+ break;
}
});
}
poll();
@@ -186,23 +186,18 @@
)
}
<div className={this.shouldShowSidebar() ?
'col-md-9' : 'col-md-12'}>
{ this.overviewJSX() }
- { this.isHitsAvailable()
- ? <Circos queries={this.state.queries}
- program={this.state.program} collapsed="true"/>
- : <span></span> }
+ { this.circosJSX() }
{
_.map(this.state.queries, _.bind(function (query) {
return (
- <Query key={"Query_"+query.id}
- program={this.state.program} querydb={this.state.querydb}
- query={query} num_queries={this.state.num_queries}
- veryBig={this.state.veryBig} selectHit={this.selectHit}
- imported_xml={this.state.imported_xml} />
- );
+ <Query key={'Query_'+query.id} query={query} showQueryCrumbs={this.state.num_queries > 1}
+ selectHit={this.selectHit} program={this.state.program} querydb={this.state.querydb}
+ veryBig={this.state.veryBig} imported_xml={this.state.imported_xml} />
+ );
}, this))
}
</div>
</div>
);
@@ -212,29 +207,40 @@
* Renders report overview.
*/
overviewJSX: function () {
return (
<div className="overview">
- <pre className="pre-reset">
+ <p className="text-monospace">
{this.state.program_version}{this.state.submitted_at
- && `; query submitted on ${this.state.submitted_at}`}
- <br/>
- Databases ({this.state.stats.nsequences} sequences,
- {this.state.stats.ncharacters} characters): {
- this.state.querydb.map((db) => { return db.title }).join(", ")
- }
- <br/>
+ && `, query submitted on ${this.state.submitted_at}`}
+ </p>
+ <p className="text-monospace">
+ Databases: {
+ this.state.querydb.map((db) => { return db.title; }).join(', ')
+ } ({this.state.stats.nsequences} sequences,
+ {this.state.stats.ncharacters} characters)
+ </p>
+ <p className="text-monospace">
Parameters: {
_.map(this.state.params, function (val, key) {
- return key + " " + val;
- }).join(", ")
+ return key + ' ' + val;
+ }).join(', ')
}
- </pre>
+ </p>
</div>
);
- },
+ },
+ /**
+ * Return JSX for circos if we have at least one hit.
+ */
+ circosJSX: function () {
+ return this.atLeastTwoHits()
+ ? <Circos queries={this.state.queries}
+ program={this.state.program} collapsed="true"/>
+ : <span></span>;
+ },
// Controller //
/**
* Returns true if results have been fetched.
@@ -243,16 +249,23 @@
*/
isResultAvailable: function () {
return this.state.queries.length >= 1;
},
- isHitsAvailable: function () {
- var cnt = 0;
- _.each(this.state.queries, function (query) {
- if(query.hits.length == 0) cnt++;
+ /**
+ * Returns true if we have at least one hit.
+ */
+ atLeastOneHit: function () {
+ return this.state.queries.some(query => query.hits.length > 0);
+ },
+
+ atLeastTwoHits: function () {
+ var hit_num = 0;
+ return this.state.queries.some(query => {
+ hit_num += query.hits.length;
+ return hit_num > 1;
});
- return !(cnt == this.state.queries.length);
},
/**
* Returns true if sidebar should be shown.
*
@@ -263,17 +276,16 @@
return !(this.state.queries.length == 1 &&
this.state.queries[0].hits.length == 0);
},
/**
- * Returns true if index should be shown in the sidebar.
- *
- * Index is not shown in the sidebar if there are more than eight queries
- * in total.
+ * Returns true if index should be shown in the sidebar. Index is shown
+ * only for 2 and 8 queries.
*/
shouldShowIndex: function () {
- return this.state.queries.length <= 8;
+ var num_queries = this.state.queries.length;
+ return num_queries >= 2 && num_queries <= 8;
},
/**
* Called after first call to render. The results may not be available at
* this stage and thus results DOM cannot be scripted here, unless using
@@ -307,11 +319,11 @@
/**
* Prevents folding of hits during text-selection.
*/
preventCollapseOnSelection: function () {
- $('body').on('mousedown', ".hit > .section-header > h4", function (event) {
+ $('body').on('mousedown', '.hit > .section-header > h4', function (event) {
var $this = $(this);
$this.on('mouseup mousemove', function handler(event) {
if (event.type === 'mouseup') {
// user wants to toggle
$this.attr('data-toggle', 'collapse');
@@ -353,22 +365,22 @@
* Adds glow to hit component.
* Updates number of Fasta that can be downloaded
*/
selectHit: function (id) {
- var checkbox = $("#" + id);
+ var checkbox = $('#' + id);
var num_checked = $('.hit-links :checkbox:checked').length;
if (!checkbox || !checkbox.val()) {
return;
}
var $hit = $(checkbox.data('target'));
// Highlight selected hit and enable 'Download FASTA/Alignment of
// selected' links.
- if (checkbox.is(":checked")) {
+ if (checkbox.is(':checked')) {
$hit.find('.section-content').addClass('glow');
$('.download-alignment-of-selected').enable();
$('.download-fasta-of-selected').enable();
}
else {
@@ -404,13 +416,17 @@
/**
* Returns the id of query.
*/
domID: function () {
- return "Query_" + this.props.query.number;
+ return 'Query_' + this.props.query.number;
},
+ queryLength: function () {
+ return this.props.query.length;
+ },
+
/**
* Returns number of hits.
*/
numhits: function () {
return this.props.query.hits.length;
@@ -421,65 +437,63 @@
render: function () {
return (
<div className="resultn" id={this.domID()}
data-query-len={this.props.query.length}
data-algorithm={this.props.program}>
- <div className="section-header">
- <h3>
- Query= {this.props.query.id}
-
- <small>
- {this.props.query.title}
- </small>
- </h3>
- <span
- className="label label-reset pos-label"
- title={"Query" + this.props.query.number + "."}
- data-toggle="tooltip">
- {this.props.query.number + "/" + this.props.num_queries}
- </span>
- </div>
- {this.numhits() &&
- (
- <div className="section-content">
- <HitsOverview key={"GO_"+this.props.query.number} query={this.props.query} program={this.props.program} collapsed={this.props.veryBig}/>
- <LengthDistribution key={"LD_"+this.props.query.id} query={this.props.query} algorithm={this.props.program} collapsed="true"/>
- <HitsTable key={"HT_"+this.props.query.number} query={this.props.query} imported_xml={this.props.imported_xml} />
- <div id="hits">
- {
- _.map(this.props.query.hits, _.bind(function (hit) {
- return (
- <Hit hit={hit}
- key={"HIT_"+hit.number}
- algorithm={this.props.program}
- querydb={this.props.querydb}
- query={this.props.query}
- imported_xml={this.props.imported_xml}
- selectHit={this.props.selectHit}/>
- );
- }, this))
- }
- </div>
- </div>
- ) || (
- <div
- className="section-content">
- <p>
- Query length: {this.props.query.length}
- </p>
- <br/>
- <br/>
- <p>
- <strong> ****** No hits found ****** </strong>
- </p>
- </div>
- )
+ { this.headerJSX() }
+ { this.numhits() && this.hitsListJSX() || this.noHitsJSX() }
+ </div>
+ );
+ },
+
+ headerJSX: function () {
+ var meta = `length: ${this.queryLength().toLocaleString()}`;
+ if (this.props.showQueryCrumbs) {
+ meta = `query ${this.props.query.number}, ` + meta;
+ }
+ return <div className="section-header">
+ <h3>
+ Query= {this.props.query.id}
+ <small>{this.props.query.title}</small>
+ </h3>
+ <span className="label label-reset pos-label">{ meta }</span>
+ </div>;
+ },
+
+ hitsListJSX: function () {
+ return <div className="section-content">
+ <HitsOverview key={'GO_' + this.props.query.number} query={this.props.query} program={this.props.program} collapsed={this.props.veryBig} />
+ <LengthDistribution key={'LD_' + this.props.query.id} query={this.props.query} algorithm={this.props.program} collapsed="true" />
+ <HitsTable key={'HT_' + this.props.query.number} query={this.props.query} imported_xml={this.props.imported_xml} />
+ <div id="hits">
+ {
+ _.map(this.props.query.hits, _.bind(function (hit) {
+ return (
+ <Hit key={'HIT_' + hit.number} hit={hit}
+ algorithm={this.props.program}
+ querydb={this.props.querydb}
+ query={this.props.query}
+ imported_xml={this.props.imported_xml}
+ selectHit={this.props.selectHit}
+ showHitCrumbs={this.numhits() > 1}
+ showQueryCrumbs={this.props.showQueryCrumbs} />
+ );
+ }, this))
}
</div>
- )
+ </div>;
},
+ noHitsJSX: function () {
+ return <div className="section-content">
+ <br />
+ <p>
+ <strong> ****** No hits found ****** </strong>
+ </p>
+ </div>;
+ },
+
shouldComponentUpdate: function (nextProps, nextState) {
if (!this.props.query) return true;
}
});
@@ -488,13 +502,13 @@
*/
var HitsTable = React.createClass({
mixins: [Utils],
render: function () {
var count = 0,
- hasName = _.every(this.props.query.hits, function(hit) {
- return hit.sciname !== '';
- });
+ hasName = _.every(this.props.query.hits, function(hit) {
+ return hit.sciname !== '';
+ });
return (
<table
className="table table-hover table-condensed tabular-view">
<thead>
@@ -512,23 +526,23 @@
<tbody>
{
_.map(this.props.query.hits, _.bind(function (hit) {
return (
<tr key={hit.number}>
- <td className="text-left">{hit.number + "."}</td>
+ <td className="text-left">{hit.number + '.'}</td>
<td>
- <a href={"#Query_" + this.props.query.number + "_hit_" + hit.number}>
+ <a href={'#Query_' + this.props.query.number + '_hit_' + hit.number}>
{hit.id}
</a>
</td>
{hasName && <td className="text-left">{hit.sciname}</td>}
{!this.props.imported_xml && <td className="text-right">{hit.qcovs}</td>}
<td className="text-right">{hit.score}</td>
<td className="text-right">{this.inExponential(hit.hsps[0].evalue)}</td>
<td className="text-right">{hit.identity}</td>
</tr>
- )
+ );
}, this))
}
</tbody>
</table>
);
@@ -549,21 +563,21 @@
},
/**
* Returns length of the hit sequence.
*/
- length: function () {
+ hitLength: function () {
return this.props.hit.length;
},
// Internal helpers. //
/**
* Returns id that will be used for the DOM node corresponding to the hit.
*/
domID: function () {
- return "Query_" + this.props.query.number + "_hit_" + this.props.hit.number;
+ return 'Query_' + this.props.query.number + '_hit_' + this.props.hit.number;
},
databaseIDs: function () {
return _.map(this.props.querydb, _.iteratee('id'));
},
@@ -591,14 +605,14 @@
downloadAlignment: function (event) {
var hsps = _.map(this.props.hit.hsps, _.bind(function (hsp) {
hsp.query_id = this.props.query.id;
hsp.hit_id = this.props.hit.id;
return hsp;
- }, this))
+ }, this));
var aln_exporter = new AlignmentExporter();
- aln_exporter.export_alignments(hsps, this.props.query.id+"_"+this.props.hit.id);
+ aln_exporter.export_alignments(hsps, this.props.query.id+'_'+this.props.hit.id);
},
// Life cycle methods //
@@ -606,11 +620,11 @@
return { showSequenceViewer: false };
},
// Return JSX for view sequence button.
viewSequenceButton: function () {
- if (this.length() > 10000) {
+ if (this.hitLength() > 10000) {
return (
<button
className="btn btn-link view-sequence disabled"
title="Sequence too long" disabled="true">
<i className="fa fa-eye"></i> Sequence
@@ -628,53 +642,61 @@
}
},
render: function () {
return (
- <div className="hit" id={this.domID()}
- data-hit-def={this.props.hit.id} data-hit-evalue={this.props.hit.evalue}
- data-hit-len={this.props.hit.length}>
- <div className="section-header">
- <h4 data-toggle="collapse"
- data-target={this.domID() + "_content"}>
- <i className="fa fa-chevron-down"></i>
-
- <span>
- {this.props.hit.id}
-
- <small>
- {this.props.hit.title}
- </small>
- </span>
- </h4>
- <span className="label label-reset pos-label"
- title={"Query " + this.props.query.number + ". Hit "
- + this.props.hit.number + " of "
- + this.props.query.hits.length + "."}
- data-toggle="tooltip">
- {this.props.hit.number + "/" + this.props.query.hits.length}
- </span>
- </div>
- <div id={this.domID() + "_content"}
- className="section-content collapse in">
- { this.hitLinks() }
- <HSPOverview key={"kablammo"+this.props.query.id}
- query={this.props.query} hit={this.props.hit}
- algorithm={this.props.algorithm}/>
- { this.hspListJSX() }
- </div>
+ <div className="hit" id={this.domID()} data-hit-def={this.props.hit.id}
+ data-hit-len={this.props.hit.length} data-hit-evalue={this.props.hit.evalue}>
+ { this.headerJSX() } { this.contentJSX() }
</div>
);
},
+ headerJSX: function () {
+ var meta = `length: ${this.hitLength().toLocaleString()}`;
+
+ if (this.props.showQueryCrumbs && this.props.showHitCrumbs) {
+ // Multiper queries, multiple hits
+ meta = `hit ${this.props.hit.number} of query ${this.props.query.number}, ` + meta;
+ }
+ else if (this.props.showQueryCrumbs && !this.props.showHitCrumbs) {
+ // Multiple queries, single hit
+ meta = `the only hit of query ${this.props.query.number}, ` + meta;
+ }
+ else if (!this.props.showQueryCrumbs && this.props.showHitCrumbs) {
+ // Single query, multiple hits
+ meta = `hit ${this.props.hit.number}, ` + meta;
+ }
+
+ return <div className="section-header">
+ <h4 data-toggle="collapse" data-target={this.domID() + '_content'}>
+ <i className="fa fa-chevron-down"></i>
+ <span>
+ {this.props.hit.id}
+ <small>{this.props.hit.title}</small>
+ </span>
+ </h4>
+ <span className="label label-reset pos-label">{ meta }</span>
+ </div>;
+ },
+
+ contentJSX: function () {
+ return <div id={this.domID() + '_content'} className="section-content collapse in">
+ { this.hitLinks() }
+ <HSPOverview key={'kablammo' + this.props.query.id} query={this.props.query}
+ hit={this.props.hit} algorithm={this.props.algorithm} />
+ { this.hspListJSX() }
+ </div>;
+ },
+
hitLinks: function () {
return (
<div className="hit-links">
<label>
- <input type="checkbox" id={this.domID() + "_checkbox"}
+ <input type="checkbox" id={this.domID() + '_checkbox'}
value={this.accession()} onChange={function () {
- this.props.selectHit(this.domID() + "_checkbox");
+ this.props.selectHit(this.domID() + '_checkbox');
}.bind(this)} data-target={'#' + this.domID()}
/> Select
</label>
{
!this.props.imported_xml && [
@@ -712,14 +734,14 @@
{
this.props.hit.hsps.map((hsp) => {
return <HSP key={hsp.number}
algorithm={this.props.algorithm}
queryNumber={this.props.query.number}
- hitNumber={this.props.hit.number} hsp={hsp}/>
+ hitNumber={this.props.hit.number} hsp={hsp}/>;
}, this)
}
- </div>
+ </div>;
}
});
/**
@@ -884,11 +906,11 @@
.done(_.bind(function (response) {
this.setState({
sequences: response.sequences,
error_msgs: response.error_msgs,
requestCompleted: true
- })
+ });
}, this))
.fail(function (jqXHR, status, error) {
showErrorModal(jqXHR, function () {
this.hide();
});
@@ -939,15 +961,15 @@
_.each(query.hits, function (hit) {
_.each(hit.hsps, function (hsp) {
hsp.hit_id = hit.id;
hsp.query_id = query.id;
hsps_arr.push(hsp);
- })
- })
+ });
+ });
}, this));
console.log('len '+hsps_arr.length);
- aln_exporter.export_alignments(hsps_arr, "alignment-"+sequence_ids.length+"_hits");
+ aln_exporter.export_alignments(hsps_arr, 'alignment-'+sequence_ids.length+'_hits');
return false;
},
downloadAlignmentOfSelected: function () {
var sequence_ids = $('.hit-links :checkbox:checked').map(function () {
@@ -965,11 +987,11 @@
hsps_arr.push(hsp);
});
}
});
}, this));
- aln_exporter.export_alignments(hsps_arr, "alignment-"+sequence_ids.length+"_hits");
+ aln_exporter.export_alignments(hsps_arr, 'alignment-'+sequence_ids.length+'_hits');
return false;
},
// JSX //
@@ -977,33 +999,33 @@
return (
<div className="sidebar">
{ this.props.shouldShowIndex && this.index() }
{ this.downloads() }
</div>
- )
+ );
},
index: function () {
return (
<div className="index">
<div
- className="section-header">
- <h4>
- { this.summary() }
- </h4>
+ className="section-header">
+ <h4>
+ { this.summary() }
+ </h4>
</div>
<ul
className="nav hover-reset active-bold">
{
_.map(this.props.data.queries, _.bind(function (query) {
return (
- <li key={"Side_bar_"+query.id}>
+ <li key={'Side_bar_'+query.id}>
<a
className="nowrap-ellipsis hover-bold"
- href={"#Query_" + query.number}
- title={"Query= " + query.id + ' ' + query.title}>
- {"Query= " + query.id}
+ href={'#Query_' + query.number}
+ title={'Query= ' + query.id + ' ' + query.title}>
+ {'Query= ' + query.id}
</a>
</li>
);
}, this))
}
@@ -1017,11 +1039,11 @@
var numqueries = this.props.data.queries.length;
var numquerydb = this.props.data.querydb.length;
return (
program.toUpperCase() + ': ' +
- numqueries + ' ' + (numqueries > 1 ? 'queries' : 'query') + ", " +
+ numqueries + ' ' + (numqueries > 1 ? 'queries' : 'query') + ', ' +
numquerydb + ' ' + (numquerydb > 1 ? 'databases' : 'database')
);
},
downloads: function () {
@@ -1066,11 +1088,11 @@
<a className="download" data-toggle="tooltip"
title="15 columns: query and subject ID; scientific
name, alignment length, mismatches, gaps, identity,
start and end coordinates, e value, bitscore, query
coverage per subject and per HSP."
- href={"download/" + this.props.data.search_id + ".std_tsv"}>
+ href={'download/' + this.props.data.search_id + '.std_tsv'}>
Standard tabular report
</a>
</li>
}
{
@@ -1078,19 +1100,19 @@
<a className="download" data-toggle="tooltip"
title="44 columns: query and subject ID, GI,
accessions, and length; alignment details;
taxonomy details of subject sequence(s) and
query coverage per subject and per HSP."
- href={"download/" + this.props.data.search_id + ".full_tsv"}>
+ href={'download/' + this.props.data.search_id + '.full_tsv'}>
Full tabular report
</a>
</li>
}
{
!this.props.data.imported_xml && <li>
<a className="download" data-toggle="tooltip"
title="Results in XML format."
- href={"download/" + this.props.data.search_id + ".xml"}>
+ href={'download/' + this.props.data.search_id + '.xml'}>
Full XML report
</a>
</li>
}
</ul>