sequenceserver 2.0.0.beta4 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +7 -4
  3. data/AppImage/sequenceserver.sh +5 -0
  4. data/lib/sequenceserver.rb +9 -5
  5. data/lib/sequenceserver/blast/job.rb +7 -24
  6. data/lib/sequenceserver/blast/report.rb +66 -33
  7. data/lib/sequenceserver/routes.rb +28 -2
  8. data/lib/sequenceserver/version.rb +1 -1
  9. data/public/SequenceServer_logo.png +0 -0
  10. data/public/css/grapher.css +8 -15
  11. data/public/css/sequenceserver.css +115 -55
  12. data/public/css/sequenceserver.min.css +3 -3
  13. data/public/js/circos.js +1 -1
  14. data/public/js/download_fasta.js +17 -0
  15. data/public/js/grapher.js +7 -9
  16. data/public/js/hit.js +217 -0
  17. data/public/js/hits_overview.js +12 -13
  18. data/public/js/hsp.js +104 -84
  19. data/public/js/{sequenceserver.js → jquery_world.js} +1 -18
  20. data/public/js/kablammo.js +337 -334
  21. data/public/js/length_distribution.js +1 -1
  22. data/public/js/query.js +147 -0
  23. data/public/js/report.js +203 -830
  24. data/public/js/search.js +176 -169
  25. data/public/js/sequence_modal.js +167 -0
  26. data/public/js/sidebar.js +210 -0
  27. data/public/js/utils.js +2 -19
  28. data/public/js/visualisation_helpers.js +2 -2
  29. data/public/sequenceserver-report.min.js +19 -19
  30. data/public/sequenceserver-search.min.js +11 -11
  31. data/public/vendor/github/twbs/bootstrap@3.3.5/js/bootstrap.js +2 -2
  32. data/spec/blast_versions/blast_2.2.30/import_spec_capybara_local_2.2.30.rb +5 -5
  33. data/spec/blast_versions/blast_2.2.31/import_spec_capybara_local_2.2.31.rb +5 -5
  34. data/spec/blast_versions/blast_2.3.0/import_spec_capybara_local_2.3.0.rb +5 -5
  35. data/spec/blast_versions/blast_2.4.0/import_spec_capybara_local_2.4.0.rb +5 -5
  36. data/spec/blast_versions/blast_2.5.0/import_spec_capybara_local_2.5.0.rb +5 -5
  37. data/spec/blast_versions/blast_2.6.0/import_spec_capybara_local_2.6.0.rb +5 -5
  38. data/spec/blast_versions/blast_2.7.1/import_spec_capybara_local_2.7.1.rb +5 -5
  39. data/spec/blast_versions/blast_2.8.1/import_spec_capybara_local_2.8.1.rb +5 -5
  40. data/spec/blast_versions/blast_2.9.0/import_spec_capybara_local_2.9.0.rb +5 -5
  41. data/spec/blast_versions/diamond_0.9.24/import_spec_capybara_local_0.9.24.rb +2 -2
  42. data/spec/capybara_spec.rb +1 -1
  43. data/views/layout.erb +1 -1
  44. metadata +9 -3
@@ -1,6 +1,6 @@
1
- import SequenceServer from './sequenceserver';
2
- import _ from 'underscore';
1
+ import './jquery_world';
3
2
  import React from 'react';
3
+ import _ from 'underscore';
4
4
 
5
5
  /**
6
6
  * Load necessary polyfills.
@@ -20,7 +20,7 @@ var Page = React.createClass({
20
20
  componentDidMount: function () {
21
21
  this.refs.dnd.setState({
22
22
  query: this.refs.form.refs.query
23
- })
23
+ });
24
24
  }
25
25
  });
26
26
 
@@ -31,14 +31,14 @@ var DnD = React.createClass({
31
31
  getInitialState: function () {
32
32
  return {
33
33
  query: null
34
- }
34
+ };
35
35
  },
36
36
 
37
37
  render: function () {
38
38
  return (
39
39
  <div
40
40
  className="dnd-overlay"
41
- style={{display: "none"}}>
41
+ style={{display: 'none'}}>
42
42
  <div
43
43
  className="container dnd-overlay-container">
44
44
  <div
@@ -47,15 +47,15 @@ var DnD = React.createClass({
47
47
  className="col-md-offset-2 col-md-10">
48
48
  <p
49
49
  className="dnd-overlay-drop"
50
- style={{display: "none"}}>
50
+ style={{display: 'none'}}>
51
51
  <i className="fa fa-2x fa-file-o"></i>
52
52
  Drop query sequence file here
53
53
  </p>
54
54
  <p
55
55
  className="dnd-overlay-overwrite"
56
- style={{display: "none"}}>
56
+ style={{display: 'none'}}>
57
57
  <i className="fa fa-2x fa-file-o"></i>
58
- <span style={{color: "red"}}>Overwrite</span> query sequence file
58
+ <span style={{color: 'red'}}>Overwrite</span> query sequence file
59
59
  </p>
60
60
 
61
61
  <div
@@ -63,7 +63,7 @@ var DnD = React.createClass({
63
63
  <div
64
64
  className="dnd-error row"
65
65
  id="dnd-multi-notification"
66
- style={{display: "none"}}>
66
+ style={{display: 'none'}}>
67
67
  <div
68
68
  className="col-md-6 col-md-offset-3">
69
69
  One file at a time please.
@@ -73,7 +73,7 @@ var DnD = React.createClass({
73
73
  <div
74
74
  className="dnd-error row"
75
75
  id="dnd-large-file-notification"
76
- style={{display: "none"}}>
76
+ style={{display: 'none'}}>
77
77
  <div
78
78
  className="col-md-6 col-md-offset-3">
79
79
  Too big a file. Can only do less than 10 MB. &gt;_&lt;
@@ -83,7 +83,7 @@ var DnD = React.createClass({
83
83
  <div
84
84
  className="dnd-error row"
85
85
  id="dnd-format-notification"
86
- style={{display: "none"}}>
86
+ style={{display: 'none'}}>
87
87
  <div
88
88
  className="col-md-6 col-md-offset-3">
89
89
  Only FASTA files please.
@@ -99,6 +99,7 @@ var DnD = React.createClass({
99
99
 
100
100
  componentDidMount: function () {
101
101
  var self = this;
102
+ var FASTA_FORMAT = /^>/;
102
103
 
103
104
  $(document).ready(function(){
104
105
  var tgtMarker = $('.dnd-overlay');
@@ -110,80 +111,80 @@ var DnD = React.createClass({
110
111
  };
111
112
 
112
113
  $(document)
113
- .on('dragenter', function (evt) {
114
+ .on('dragenter', function (evt) {
114
115
  // Do not activate DnD if a modal is active.
115
- if ($.modalActive()) return;
116
+ if ($.modalActive()) return;
116
117
 
117
- // Based on http://stackoverflow.com/a/8494918/1205465.
118
- // Contrary to what the above link says, the snippet below can't
119
- // distinguish directories from files. We handle that on drop.
120
- var dt = evt.originalEvent.dataTransfer;
121
- var isFile = dt.types && ((dt.types.indexOf && // Chrome and Safari
118
+ // Based on http://stackoverflow.com/a/8494918/1205465.
119
+ // Contrary to what the above link says, the snippet below can't
120
+ // distinguish directories from files. We handle that on drop.
121
+ var dt = evt.originalEvent.dataTransfer;
122
+ var isFile = dt.types && ((dt.types.indexOf && // Chrome and Safari
122
123
  dt.types.indexOf('Files') != -1) ||
123
124
  (dt.types.contains && // Firefox
124
125
  dt.types.contains('application/x-moz-file')));
125
126
 
126
- if (!isFile) { return; }
127
+ if (!isFile) { return; }
127
128
 
128
- $('.dnd-error').hide();
129
- tgtMarker.stop(true, true);
130
- tgtMarker.show();
131
- dt.effectAllowed = 'copy';
132
- if (self.state.query.isEmpty()) {
133
- $('.dnd-overlay-overwrite').hide();
134
- $('.dnd-overlay-drop').show('drop', {direction: 'down'}, 'fast');
135
- }
136
- else {
129
+ $('.dnd-error').hide();
130
+ tgtMarker.stop(true, true);
131
+ tgtMarker.show();
132
+ dt.effectAllowed = 'copy';
133
+ if (self.state.query.isEmpty()) {
134
+ $('.dnd-overlay-overwrite').hide();
135
+ $('.dnd-overlay-drop').show('drop', {direction: 'down'}, 'fast');
136
+ }
137
+ else {
138
+ $('.dnd-overlay-drop').hide();
139
+ $('.dnd-overlay-overwrite').show('drop', {direction: 'down'}, 'fast');
140
+ }
141
+ })
142
+ .on('dragleave', '.dnd-overlay', function (evt) {
143
+ tgtMarker.hide();
137
144
  $('.dnd-overlay-drop').hide();
138
- $('.dnd-overlay-overwrite').show('drop', {direction: 'down'}, 'fast');
139
- }
140
- })
141
- .on('dragleave', '.dnd-overlay', function (evt) {
142
- tgtMarker.hide();
143
- $('.dnd-overlay-drop').hide();
144
- $('.dnd-overlay-overwrite').hide();
145
- })
146
- .on('dragover', '.dnd-overlay', function (evt) {
147
- evt.originalEvent.dataTransfer.dropEffect = 'copy';
148
- evt.preventDefault();
149
- })
150
- .on('drop', '.dnd-overlay', function (evt) {
151
- evt.preventDefault();
152
- evt.stopPropagation();
153
-
154
- var indicator = $('#sequence-file');
155
- self.state.query.focus();
156
-
157
- var files = evt.originalEvent.dataTransfer.files;
158
- if (files.length > 1) {
159
- dndError('dnd-multi');
160
- return;
161
- }
145
+ $('.dnd-overlay-overwrite').hide();
146
+ })
147
+ .on('dragover', '.dnd-overlay', function (evt) {
148
+ evt.originalEvent.dataTransfer.dropEffect = 'copy';
149
+ evt.preventDefault();
150
+ })
151
+ .on('drop', '.dnd-overlay', function (evt) {
152
+ evt.preventDefault();
153
+ evt.stopPropagation();
154
+
155
+ var indicator = $('#sequence-file');
156
+ self.state.query.focus();
157
+
158
+ var files = evt.originalEvent.dataTransfer.files;
159
+ if (files.length > 1) {
160
+ dndError('dnd-multi');
161
+ return;
162
+ }
162
163
 
163
- var file = files[0];
164
- if (file.size > 10 * 1048576) {
165
- dndError('dnd-large-file');
166
- return;
167
- }
164
+ var file = files[0];
165
+ if (file.size > 10 * 1048576) {
166
+ dndError('dnd-large-file');
167
+ return;
168
+ }
168
169
 
169
- var reader = new FileReader();
170
- reader.onload = function (e) {
171
- var content = e.target.result;
172
- if (SequenceServer.FASTA_FORMAT.test(content)) {
173
- indicator.text(file.name + ' ');
174
- self.state.query.value(content);
175
- tgtMarker.hide();
176
- } else {
170
+ var reader = new FileReader();
171
+ reader.onload = function (e) {
172
+ var content = e.target.result;
173
+ if (FASTA_FORMAT.test(content)) {
174
+ indicator.text(file.name + ' ');
175
+ self.state.query.value(content);
176
+ tgtMarker.hide();
177
+ } else {
177
178
  // apparently not FASTA
178
- dndError('dnd-format');
179
- }
180
- };
181
- reader.onerror = function (e) {
179
+ dndError('dnd-format');
180
+ }
181
+ };
182
+ reader.onerror = function (e) {
182
183
  // Couldn't read. Means dropped stuff wasn't FASTA file.
183
- dndError('dnd-format');
184
- };
185
- reader.readAsText(file);
186
- });
184
+ dndError('dnd-format');
185
+ };
186
+ reader.readAsText(file);
187
+ });
187
188
  });
188
189
  }
189
190
  });
@@ -203,32 +204,39 @@ var Form = React.createClass({
203
204
  componentDidMount: function () {
204
205
  /* Fetch data to initialise the search interface from the server. These
205
206
  * include list of databases to search against, advanced options to
206
- * apply when an algorithm is selected, and a query sequenced that
207
+ * apply when an algorithm is selected, and a query sequence that
207
208
  * the user may want to search in the databases.
208
209
  */
209
- $.getJSON("searchdata.json" + window.location.search, function(data) {
210
+ var search = location.search.split(/\?|&/).filter(Boolean);
211
+ var job_id = sessionStorage.getItem('job_id');
212
+ if (job_id) {
213
+ search.unshift(`job_id=${job_id}`);
214
+ }
215
+ $.getJSON(`searchdata.json?${search.join('&')}`, function(data) {
210
216
  /* Update form state (i.e., list of databases and predefined
211
217
  * advanced options.
212
218
  */
213
219
  this.setState({
214
- databases: data["database"], preDefinedOpts: data["options"]
220
+ databases: data['database'],
221
+ preSelectedDbs: data['preSelectedDbs'],
222
+ preDefinedOpts: data['options']
215
223
  });
216
224
 
217
225
  /* Pre-populate the form with server sent query sequences
218
226
  * (if any).
219
227
  */
220
- if (data["query"]) {
221
- this.refs.query.value(data["query"]);
228
+ if (data['query']) {
229
+ this.refs.query.value(data['query']);
222
230
  }
223
231
  }.bind(this));
224
232
 
225
233
  /* Enable submitting form on Cmd+Enter */
226
- $(document).bind("keydown", _.bind(function (e) {
227
- if (e.ctrlKey && e.keyCode === 13 &&
234
+ $(document).bind('keydown', _.bind(function (e) {
235
+ if (e.ctrlKey && e.keyCode === 13 &&
228
236
  !$('#method').is(':disabled')) {
229
- $(this.getDOMNode()).trigger('submit');
230
- }
231
- }, this));
237
+ $(this.getDOMNode()).trigger('submit');
238
+ }
239
+ }, this));
232
240
  },
233
241
 
234
242
  determineBlastMethod: function () {
@@ -241,26 +249,26 @@ var Form = React.createClass({
241
249
 
242
250
  //database type is always known
243
251
  switch (database_type) {
252
+ case 'protein':
253
+ switch (sequence_type) {
254
+ case undefined:
255
+ return ['blastp', 'blastx'];
244
256
  case 'protein':
245
- switch (sequence_type) {
246
- case undefined:
247
- return ['blastp', 'blastx'];
248
- case 'protein':
249
- return ['blastp'];
250
- case 'nucleotide':
251
- return ['blastx'];
252
- }
253
- break;
257
+ return ['blastp'];
254
258
  case 'nucleotide':
255
- switch (sequence_type) {
256
- case undefined:
257
- return ['tblastn', 'blastn', 'tblastx'];
258
- case 'protein':
259
- return ['tblastn'];
260
- case 'nucleotide':
261
- return ['blastn', 'tblastx'];
262
- }
263
- break;
259
+ return ['blastx'];
260
+ }
261
+ break;
262
+ case 'nucleotide':
263
+ switch (sequence_type) {
264
+ case undefined:
265
+ return ['tblastn', 'blastn', 'tblastx'];
266
+ case 'protein':
267
+ return ['tblastn'];
268
+ case 'nucleotide':
269
+ return ['blastn', 'tblastx'];
270
+ }
271
+ break;
264
272
  }
265
273
 
266
274
  return [];
@@ -285,35 +293,32 @@ var Form = React.createClass({
285
293
  },
286
294
 
287
295
  handleAlgoChanged: function (algo) {
288
- if (this.state.preDefinedOpts.hasOwnProperty(algo)) {
289
- this.refs.opts.setState({
290
- preOpts: this.state.preDefinedOpts[algo].join(" ")
291
- });
292
- }
293
- else {
294
- this.refs.opts.setState({preOpts: ""});
295
- }
296
+ if (this.state.preDefinedOpts.hasOwnProperty(algo)) {
297
+ this.refs.opts.setState({
298
+ preOpts: this.state.preDefinedOpts[algo].join(' ')
299
+ });
300
+ }
301
+ else {
302
+ this.refs.opts.setState({preOpts: ''});
303
+ }
296
304
  },
297
305
 
298
306
  render: function () {
299
307
  return (
300
- <div
301
- className="container">
302
- <form
303
- id="blast" method="post" className="form-horizontal">
304
- <div
305
- className="form-group query-container">
308
+ <div className="container">
309
+ <form id="blast" method="post" className="form-horizontal">
310
+ <div className="form-group query-container">
306
311
  <Query ref="query" onSequenceTypeChanged={this.handleSequenceTypeChanged}/>
307
312
  </div>
308
- <div
309
- className="notifications" id="notifications">
313
+ <div className="notifications" id="notifications">
310
314
  <NucleotideNotification/>
311
315
  <ProteinNotification/>
312
316
  <MixedNotification/>
313
317
  </div>
314
- <Databases ref="databases" onDatabaseTypeChanged={this.handleDatabaseTypeChanaged} databases={this.state.databases}/>
315
- <div
316
- className="form-group">
318
+ <Databases ref="databases" databases={this.state.databases}
319
+ preSelectedDbs={this.state.preSelectedDbs}
320
+ onDatabaseTypeChanged={this.handleDatabaseTypeChanaged} />
321
+ <div className="form-group">
317
322
  <Options ref="opts"/>
318
323
  <SearchButton ref="button" onAlgoChanged={this.handleAlgoChanged}/>
319
324
  </div>
@@ -346,7 +351,7 @@ var Query = React.createClass({
346
351
  else {
347
352
  this.setState({
348
353
  value: val
349
- })
354
+ });
350
355
  return this;
351
356
  }
352
357
  },
@@ -451,12 +456,12 @@ var Query = React.createClass({
451
456
  if (!tmp) { continue; }
452
457
 
453
458
  if (!type) {
454
- // successfully guessed the type of atleast one sequence
455
- type = tmp;
459
+ // successfully guessed the type of atleast one sequence
460
+ type = tmp;
456
461
  }
457
462
  else if (tmp !== type) {
458
- // user has mixed different type of sequences
459
- return 'mixed';
463
+ // user has mixed different type of sequences
464
+ return 'mixed';
460
465
  }
461
466
  }
462
467
 
@@ -511,7 +516,7 @@ var Query = React.createClass({
511
516
  // Lifecycle methods. //
512
517
 
513
518
  getInitialState: function () {
514
- var input_sequence = $("input#input_sequence").val() || '';
519
+ var input_sequence = $('input#input_sequence').val() || '';
515
520
  return {
516
521
  value: input_sequence
517
522
  };
@@ -619,10 +624,7 @@ var MixedNotification = React.createClass({
619
624
 
620
625
  var Databases = React.createClass({
621
626
  getInitialState: function () {
622
- return {
623
- type: '',
624
- databases: []
625
- }
627
+ return { type: '' };
626
628
  },
627
629
 
628
630
  databases: function (category) {
@@ -631,9 +633,9 @@ var Databases = React.createClass({
631
633
  }
632
634
 
633
635
  return _.select(this.props.databases,
634
- function (database) {
635
- return database.type === category;
636
- });
636
+ function (database) {
637
+ return database.type === category;
638
+ });
637
639
  },
638
640
 
639
641
  nselected: function () {
@@ -642,7 +644,7 @@ var Databases = React.createClass({
642
644
 
643
645
  categories: function () {
644
646
  return _.uniq(_.map(this.props.databases,
645
- _.iteratee('type'))).sort();
647
+ _.iteratee('type'))).sort();
646
648
  },
647
649
 
648
650
  handleClick: function (database) {
@@ -652,12 +654,12 @@ var Databases = React.createClass({
652
654
 
653
655
  handleToggle: function (toggleState, type) {
654
656
  switch (toggleState) {
655
- case '[Select all]':
656
- $(`.${type} .database input:not(:checked)`).click();
657
- break;
658
- case '[Deselect all]':
659
- $(`.${type} .database input:checked`).click();
660
- break;
657
+ case '[Select all]':
658
+ $(`.${type} .database input:not(:checked)`).click();
659
+ break;
660
+ case '[Deselect all]':
661
+ $(`.${type} .database input:checked`).click();
662
+ break;
661
663
  }
662
664
  },
663
665
 
@@ -672,13 +674,13 @@ var Databases = React.createClass({
672
674
  renderDatabases: function (category) {
673
675
  // Panel name and column width.
674
676
  var panelTitle = category[0].toUpperCase() +
675
- category.substring(1).toLowerCase() + " databases";
677
+ category.substring(1).toLowerCase() + ' databases';
676
678
  var columnClass = this.categories().length === 1 ? 'col-md-12' :
677
679
  'col-md-6';
678
680
 
679
681
  // Toggle button.
680
682
  var toggleState = '[Select all]';
681
- var toggleClass = 'btn btn-link';
683
+ var toggleClass = 'btn-link';
682
684
  var toggleShown = this.databases(category).length > 1 ;
683
685
  var toggleDisabled = this.state.type && this.state.type !== category;
684
686
  if (toggleShown && toggleDisabled) toggleClass += ' disabled';
@@ -689,20 +691,20 @@ var Databases = React.createClass({
689
691
 
690
692
  // JSX.
691
693
  return (
692
- <div className={columnClass} key={"DB_"+category}>
694
+ <div className={columnClass} key={'DB_'+category}>
693
695
  <div className="panel panel-default">
694
696
  <div className="panel-heading">
695
- <h4 style={{display: "inline"}}>{panelTitle}</h4> &nbsp;&nbsp;
697
+ <h4 style={{display: 'inline'}}>{panelTitle}</h4> &nbsp;&nbsp;
696
698
  <button type="button" className={toggleClass} disabled={toggleDisabled}
697
- onClick={ function () { this.handleToggle(toggleState, category) }.bind(this) }>
699
+ onClick={ function () { this.handleToggle(toggleState, category); }.bind(this) }>
698
700
  {toggleState}
699
701
  </button>
700
702
  </div>
701
- <ul className={"list-group databases " + category}>
703
+ <ul className={'list-group databases ' + category}>
702
704
  {
703
705
  _.map(this.databases(category), _.bind(function (database,index) {
704
706
  return (
705
- <li className="list-group-item" key={"DB_"+category+index}>
707
+ <li className="list-group-item" key={'DB_'+category+index}>
706
708
  { this.renderDatabase(database) }
707
709
  </li>
708
710
  );
@@ -724,18 +726,18 @@ var Databases = React.createClass({
724
726
  type="checkbox" name="databases[]" value={database.id}
725
727
  data-type={database.type} disabled={disabled}
726
728
  onChange=
727
- {
728
- _.bind(function () {
729
- this.handleClick(database)
730
- }, this)
731
- }/>
732
- {" " + (database.title || database.name)}
729
+ {
730
+ _.bind(function () {
731
+ this.handleClick(database);
732
+ }, this)
733
+ }/>
734
+ {' ' + (database.title || database.name)}
733
735
  </label>
734
736
  );
735
737
  },
736
738
 
737
739
  //shouldComponentUpdate: function (props, state) {
738
- //return !(state.type && state.type === this.state.type);
740
+ //return !(state.type && state.type === this.state.type);
739
741
  //},
740
742
 
741
743
  componentDidUpdate: function () {
@@ -744,6 +746,11 @@ var Databases = React.createClass({
744
746
  this.handleClick(this.databases()[0]);
745
747
  }
746
748
 
749
+ if (this.props.preSelectedDbs) {
750
+ var selectors = this.props.preSelectedDbs.map(db => `input[value=${db.id}]`);
751
+ $(...selectors).prop('checked',true);
752
+ setTimeout(() => this.handleClick(this.props.preSelectedDbs[0]));
753
+ }
747
754
  this.props.onDatabaseTypeChanged(this.state.type);
748
755
  }
749
756
  });
@@ -751,15 +758,15 @@ var Databases = React.createClass({
751
758
  var Options = React.createClass({
752
759
 
753
760
  updateBox: function (evt) {
754
- this.setState({
755
- preOpts: evt.target.value
756
- });
761
+ this.setState({
762
+ preOpts: evt.target.value
763
+ });
757
764
  },
758
765
 
759
766
  getInitialState: function () {
760
- return {
761
- preOpts: ""
762
- }
767
+ return {
768
+ preOpts: ''
769
+ };
763
770
  },
764
771
 
765
772
  render: function () {
@@ -784,7 +791,7 @@ var Options = React.createClass({
784
791
  placeholder="eg: -evalue 1.0e-5 -num_alignments 100"
785
792
  value={this.state.preOpts}
786
793
  onChange={this.updateBox}
787
- />
794
+ />
788
795
  <div
789
796
  className="input-group-addon cursor-pointer"
790
797
  data-toggle="modal" data-target="#help">
@@ -827,23 +834,23 @@ var SearchButton = React.createClass({
827
834
  trigger: 'manual',
828
835
  title: _.bind(function () {
829
836
  if (!this.state.hasQuery && !this.state.hasDatabases) {
830
- return "You must enter a query sequence and select one or more databases above before you can run a search!";
837
+ return 'You must enter a query sequence and select one or more databases above before you can run a search!';
831
838
  }
832
839
  else if (this.state.hasQuery && !this.state.hasDatabases) {
833
- return "You must select one or more databases above before you can run a search!";
840
+ return 'You must select one or more databases above before you can run a search!';
834
841
  }
835
842
  else if (!this.state.hasQuery && this.state.hasDatabases) {
836
- return "You must enter a query sequence above before you can run a search!";
843
+ return 'You must enter a query sequence above before you can run a search!';
837
844
  }
838
845
  }, this)
839
846
  });
840
847
 
841
848
  this.submitButton().tooltip({
842
849
  title: _.bind(function () {
843
- var title = "Click to BLAST or press Ctrl+Enter.";
850
+ var title = 'Click to BLAST or press Ctrl+Enter.';
844
851
  if (this.state.methods.length > 1) {
845
- title += " Click dropdown button on the right for other" +
846
- " BLAST algorithms that can be used.";
852
+ title += ' Click dropdown button on the right for other' +
853
+ ' BLAST algorithms that can be used.';
847
854
  }
848
855
  return title;
849
856
  }, this)
@@ -902,7 +909,7 @@ var SearchButton = React.createClass({
902
909
  methods: [],
903
910
  hasQuery: false,
904
911
  hasDatabases: false
905
- }
912
+ };
906
913
  },
907
914
 
908
915
  render: function () {
@@ -970,7 +977,7 @@ var SearchButton = React.createClass({
970
977
  this.props.onAlgoChanged(this.state.methods[0]);
971
978
  }
972
979
  else {
973
- this.props.onAlgoChanged("");
980
+ this.props.onAlgoChanged('');
974
981
  }
975
982
  }
976
983
  });