sequenceserver 0.8.9 → 1.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (213) hide show
  1. checksums.yaml +4 -4
  2. data/{README.txt → README.md} +2 -0
  3. data/bin/sequenceserver +255 -55
  4. data/config.ru +2 -4
  5. data/lib/sequenceserver.rb +293 -447
  6. data/lib/sequenceserver/blast.rb +464 -64
  7. data/lib/sequenceserver/database.rb +185 -19
  8. data/lib/sequenceserver/links.rb +114 -0
  9. data/lib/sequenceserver/logger.rb +27 -0
  10. data/lib/sequenceserver/sequence.rb +141 -0
  11. data/public/css/bootstrap.min.css +8 -413
  12. data/public/css/custom.css +363 -122
  13. data/public/css/font-awesome.min.css +4 -0
  14. data/public/fonts/FontAwesome.otf +0 -0
  15. data/public/fonts/fontawesome-webfont.eot +0 -0
  16. data/public/fonts/fontawesome-webfont.svg +565 -0
  17. data/public/fonts/fontawesome-webfont.ttf +0 -0
  18. data/public/fonts/fontawesome-webfont.woff +0 -0
  19. data/public/fonts/fontawesome-webfont.woff2 +0 -0
  20. data/public/js/bootstrap.min.js +11 -0
  21. data/public/js/d3.v3.min.js +5 -0
  22. data/public/js/html5shiv.min.js +4 -0
  23. data/public/js/jquery.scrollspy.js +74 -0
  24. data/public/js/jquery.t.js +353 -0
  25. data/public/js/sequence.js +2419 -0
  26. data/public/js/sequenceserver.blast.js +29 -30
  27. data/public/js/sequenceserver.js +544 -120
  28. data/public/js/underscore.min.js +6 -0
  29. data/public/js/webshims/polyfiller.js +1 -0
  30. data/public/js/webshims/shims/FlashCanvas/canvas2png.js +1 -0
  31. data/public/js/webshims/shims/FlashCanvas/flashcanvas.js +1 -0
  32. data/public/js/webshims/shims/FlashCanvas/flashcanvas.swf +0 -0
  33. data/public/js/webshims/shims/FlashCanvasPro/canvas2png.js +1 -0
  34. data/public/js/webshims/shims/FlashCanvasPro/flash10canvas.swf +0 -0
  35. data/public/js/webshims/shims/FlashCanvasPro/flash9canvas.swf +0 -0
  36. data/public/js/webshims/shims/FlashCanvasPro/flashcanvas.js +1 -0
  37. data/public/js/webshims/shims/canvas-blob.js +1 -0
  38. data/public/js/webshims/shims/color-picker.js +2 -0
  39. data/public/js/webshims/shims/combos/1.js +6 -0
  40. data/public/js/webshims/shims/combos/10.js +2 -0
  41. data/public/js/webshims/shims/combos/11.js +2 -0
  42. data/public/js/webshims/shims/combos/12.js +6 -0
  43. data/public/js/webshims/shims/combos/13.js +1 -0
  44. data/public/js/webshims/shims/combos/14.js +1 -0
  45. data/public/js/webshims/shims/combos/15.js +2 -0
  46. data/public/js/webshims/shims/combos/16.js +7 -0
  47. data/public/js/webshims/shims/combos/17.js +2 -0
  48. data/public/js/webshims/shims/combos/18.js +3 -0
  49. data/public/js/webshims/shims/combos/2.js +7 -0
  50. data/public/js/webshims/shims/combos/21.js +2 -0
  51. data/public/js/webshims/shims/combos/22.js +1 -0
  52. data/public/js/webshims/shims/combos/23.js +6 -0
  53. data/public/js/webshims/shims/combos/25.js +2 -0
  54. data/public/js/webshims/shims/combos/27.js +1 -0
  55. data/public/js/webshims/shims/combos/28.js +1 -0
  56. data/public/js/webshims/shims/combos/29.js +1 -0
  57. data/public/js/webshims/shims/combos/3.js +1 -0
  58. data/public/js/webshims/shims/combos/30.js +2 -0
  59. data/public/js/webshims/shims/combos/31.js +1 -0
  60. data/public/js/webshims/shims/combos/33.js +1 -0
  61. data/public/js/webshims/shims/combos/34.js +1 -0
  62. data/public/js/webshims/shims/combos/4.js +1 -0
  63. data/public/js/webshims/shims/combos/5.js +2 -0
  64. data/public/js/webshims/shims/combos/6.js +2 -0
  65. data/public/js/webshims/shims/combos/7.js +7 -0
  66. data/public/js/webshims/shims/combos/8.js +7 -0
  67. data/public/js/webshims/shims/combos/9.js +2 -0
  68. data/public/js/webshims/shims/combos/97.js +1 -0
  69. data/public/js/webshims/shims/combos/98.js +1 -0
  70. data/public/js/webshims/shims/combos/99.js +1 -0
  71. data/public/js/webshims/shims/details.js +1 -0
  72. data/public/js/webshims/shims/dom-extend.js +1 -0
  73. data/public/js/webshims/shims/es5.js +1 -0
  74. data/public/js/webshims/shims/es6.js +1 -0
  75. data/public/js/webshims/shims/excanvas.js +1 -0
  76. data/public/js/webshims/shims/filereader-xhr.js +1 -0
  77. data/public/js/webshims/shims/form-combat.js +1 -0
  78. data/public/js/webshims/shims/form-core.js +1 -0
  79. data/public/js/webshims/shims/form-datalist-lazy.js +1 -0
  80. data/public/js/webshims/shims/form-datalist.js +1 -0
  81. data/public/js/webshims/shims/form-fixrangechange.js +1 -0
  82. data/public/js/webshims/shims/form-inputmode.js +1 -0
  83. data/public/js/webshims/shims/form-message.js +1 -0
  84. data/public/js/webshims/shims/form-native-extend.js +1 -0
  85. data/public/js/webshims/shims/form-number-date-api.js +1 -0
  86. data/public/js/webshims/shims/form-number-date-ui.js +1 -0
  87. data/public/js/webshims/shims/form-shim-extend.js +1 -0
  88. data/public/js/webshims/shims/form-shim-extend2.js +1 -0
  89. data/public/js/webshims/shims/form-validation.js +1 -0
  90. data/public/js/webshims/shims/form-validators.js +1 -0
  91. data/public/js/webshims/shims/forms-picker.js +1 -0
  92. data/public/js/webshims/shims/geolocation.js +1 -0
  93. data/public/js/webshims/shims/i18n/formcfg-ar.js +1 -0
  94. data/public/js/webshims/shims/i18n/formcfg-ch-CN.js +1 -0
  95. data/public/js/webshims/shims/i18n/formcfg-cs.js +1 -0
  96. data/public/js/webshims/shims/i18n/formcfg-de.js +1 -0
  97. data/public/js/webshims/shims/i18n/formcfg-el.js +1 -0
  98. data/public/js/webshims/shims/i18n/formcfg-en.js +1 -0
  99. data/public/js/webshims/shims/i18n/formcfg-es.js +1 -0
  100. data/public/js/webshims/shims/i18n/formcfg-fa.js +1 -0
  101. data/public/js/webshims/shims/i18n/formcfg-fr.js +1 -0
  102. data/public/js/webshims/shims/i18n/formcfg-he.js +1 -0
  103. data/public/js/webshims/shims/i18n/formcfg-hi.js +1 -0
  104. data/public/js/webshims/shims/i18n/formcfg-hu.js +1 -0
  105. data/public/js/webshims/shims/i18n/formcfg-it.js +1 -0
  106. data/public/js/webshims/shims/i18n/formcfg-ja.js +1 -0
  107. data/public/js/webshims/shims/i18n/formcfg-lt.js +1 -0
  108. data/public/js/webshims/shims/i18n/formcfg-nl.js +1 -0
  109. data/public/js/webshims/shims/i18n/formcfg-pl.js +1 -0
  110. data/public/js/webshims/shims/i18n/formcfg-pt-BR.js +1 -0
  111. data/public/js/webshims/shims/i18n/formcfg-pt-PT.js +1 -0
  112. data/public/js/webshims/shims/i18n/formcfg-pt.js +1 -0
  113. data/public/js/webshims/shims/i18n/formcfg-ru.js +1 -0
  114. data/public/js/webshims/shims/i18n/formcfg-sv.js +1 -0
  115. data/public/js/webshims/shims/i18n/formcfg-zh-CN.js +1 -0
  116. data/public/js/webshims/shims/i18n/formcfg-zh-TW.js +1 -0
  117. data/public/js/webshims/shims/jme/alternate-media.js +1 -0
  118. data/public/js/webshims/shims/jme/base.js +1 -0
  119. data/public/js/webshims/shims/jme/controls.css +1 -0
  120. data/public/js/webshims/shims/jme/jme.eot +0 -0
  121. data/public/js/webshims/shims/jme/jme.svg +36 -0
  122. data/public/js/webshims/shims/jme/jme.ttf +0 -0
  123. data/public/js/webshims/shims/jme/jme.woff +0 -0
  124. data/public/js/webshims/shims/jme/mediacontrols-lazy.js +1 -0
  125. data/public/js/webshims/shims/jme/mediacontrols.js +1 -0
  126. data/public/js/webshims/shims/jme/playlist.js +1 -0
  127. data/public/js/webshims/shims/jpicker/images/AlphaBar.png +0 -0
  128. data/public/js/webshims/shims/jpicker/images/Bars.png +0 -0
  129. data/public/js/webshims/shims/jpicker/images/Maps.png +0 -0
  130. data/public/js/webshims/shims/jpicker/images/NoColor.png +0 -0
  131. data/public/js/webshims/shims/jpicker/images/bar-opacity.png +0 -0
  132. data/public/js/webshims/shims/jpicker/images/map-opacity.png +0 -0
  133. data/public/js/webshims/shims/jpicker/images/mappoint.gif +0 -0
  134. data/public/js/webshims/shims/jpicker/images/picker.gif +0 -0
  135. data/public/js/webshims/shims/jpicker/images/preview-opacity.png +0 -0
  136. data/public/js/webshims/shims/jpicker/images/rangearrows.gif +0 -0
  137. data/public/js/webshims/shims/jpicker/jpicker.css +1 -0
  138. data/public/js/webshims/shims/matchMedia.js +3 -0
  139. data/public/js/webshims/shims/mediacapture-picker.js +1 -0
  140. data/public/js/webshims/shims/mediacapture.js +1 -0
  141. data/public/js/webshims/shims/mediaelement-core.js +1 -0
  142. data/public/js/webshims/shims/mediaelement-debug.js +1 -0
  143. data/public/js/webshims/shims/mediaelement-jaris.js +1 -0
  144. data/public/js/webshims/shims/mediaelement-native-fix.js +1 -0
  145. data/public/js/webshims/shims/mediaelement-yt.js +1 -0
  146. data/public/js/webshims/shims/moxie/flash/Moxie.cdn.swf +0 -0
  147. data/public/js/webshims/shims/moxie/flash/Moxie.min.swf +0 -0
  148. data/public/js/webshims/shims/moxie/js/moxie-html4.js +3 -0
  149. data/public/js/webshims/shims/moxie/js/moxie-swf.js +2 -0
  150. data/public/js/webshims/shims/picture.js +1 -0
  151. data/public/js/webshims/shims/plugins/jquery.ui.position.js +11 -0
  152. data/public/js/webshims/shims/range-ui.js +1 -0
  153. data/public/js/webshims/shims/sizzle.js +11 -0
  154. data/public/js/webshims/shims/sticky.js +1 -0
  155. data/public/js/webshims/shims/styles/color-picker.png +0 -0
  156. data/public/js/webshims/shims/styles/forms-ext.css +1 -0
  157. data/public/js/webshims/shims/styles/forms-picker.css +1 -0
  158. data/public/js/webshims/shims/styles/progress.gif +0 -0
  159. data/public/js/webshims/shims/styles/progress.png +0 -0
  160. data/public/js/webshims/shims/styles/shim-ext.css +1 -0
  161. data/public/js/webshims/shims/styles/shim.css +1 -0
  162. data/public/js/webshims/shims/styles/transparent.png +0 -0
  163. data/public/js/webshims/shims/styles/widget.eot +0 -0
  164. data/public/js/webshims/shims/styles/widget.svg +12 -0
  165. data/public/js/webshims/shims/styles/widget.ttf +0 -0
  166. data/public/js/webshims/shims/styles/widget.woff +0 -0
  167. data/public/js/webshims/shims/swf/JarisFLVPlayer.swf +0 -0
  168. data/public/js/webshims/shims/swfmini-embed.js +1 -0
  169. data/public/js/webshims/shims/swfmini.js +6 -0
  170. data/public/js/webshims/shims/track-ui.js +1 -0
  171. data/public/js/webshims/shims/track.js +1 -0
  172. data/public/js/webshims/shims/url.js +1 -0
  173. data/public/js/webshims/shims/usermedia-core.js +1 -0
  174. data/public/js/webshims/shims/usermedia-shim.js +1 -0
  175. data/sequenceserver.gemspec +16 -13
  176. data/views/400.erb +28 -0
  177. data/views/500.erb +35 -19
  178. data/views/_options.erb +6 -15
  179. data/views/result.erb +218 -0
  180. data/views/search.erb +354 -151
  181. metadata +254 -62
  182. data/example.config.yml +0 -39
  183. data/lib/sequenceserver/customisation.rb +0 -60
  184. data/lib/sequenceserver/database_formatter.rb +0 -190
  185. data/lib/sequenceserver/helpers.rb +0 -136
  186. data/lib/sequenceserver/sequencehelpers.rb +0 -93
  187. data/lib/sequenceserver/sinatralikeloggerformatter.rb +0 -12
  188. data/lib/sequenceserver/version.rb +0 -9
  189. data/public/css/beige.css.css +0 -254
  190. data/public/css/bootstrap.dropdown.css +0 -29
  191. data/public/css/bootstrap.icons.css +0 -155
  192. data/public/css/bootstrap.modal.css +0 -28
  193. data/public/js/bootstrap.dropdown.js +0 -92
  194. data/public/js/bootstrap.modal.js +0 -7
  195. data/public/js/bootstrap.transition.js +0 -7
  196. data/public/js/jquery-scrollspy.js +0 -98
  197. data/public/js/jquery.activity.js +0 -10
  198. data/public/js/jquery.enablePlaceholder.min.js +0 -10
  199. data/public/js/store.min.js +0 -2
  200. data/public/sequence.html +0 -28
  201. data/tests/database/nucleotide/Sinvicta2-2-3.cdna.subset.fasta +0 -5486
  202. data/tests/database/nucleotide/Sinvicta2-2-3.cdna.subset.fasta.nhr +0 -0
  203. data/tests/database/nucleotide/Sinvicta2-2-3.cdna.subset.fasta.nin +0 -0
  204. data/tests/database/nucleotide/Sinvicta2-2-3.cdna.subset.fasta.nsq +0 -0
  205. data/tests/database/protein/Sinvicta2-2-3.prot.subset.fasta +0 -6449
  206. data/tests/database/protein/Sinvicta2-2-3.prot.subset.fasta.phr +0 -0
  207. data/tests/database/protein/Sinvicta2-2-3.prot.subset.fasta.pin +0 -0
  208. data/tests/database/protein/Sinvicta2-2-3.prot.subset.fasta.psq +0 -0
  209. data/tests/run +0 -26
  210. data/tests/test_sequencehelpers.rb +0 -77
  211. data/tests/test_sequenceserver_blast.rb +0 -60
  212. data/tests/test_ui.rb +0 -104
  213. data/tests/ui.specs.todo +0 -10
@@ -96,21 +96,21 @@ SS.blast = (function () {
96
96
  return true;
97
97
  }
98
98
 
99
- /*
100
- determine input sequence type, and trigger 'sequence_type_changed'
101
- event if the input sequence type has changed
102
- */
99
+ /**
100
+ * Determine input sequence type, and trigger 'sequence_type_changed' event
101
+ * if the input sequence type has changed, or cannot be determined.
102
+ */
103
103
  var signal_sequence_type_changed = function () {
104
- var type, tmp;
104
+ var type, _type;
105
105
 
106
106
  $('#sequence').change(function () {
107
- tmp = type_of_sequences();
107
+ _type = type_of_sequences();
108
108
 
109
- if (tmp != type){
110
- type = tmp;
109
+ if (!_type || _type !== type) {
110
+ type = _type;
111
111
 
112
- //notify listeners
113
- $(this).trigger('sequence_type_changed', type);
112
+ //notify listeners
113
+ $(this).trigger('sequence_type_changed', type);
114
114
  }
115
115
  });
116
116
  };
@@ -131,39 +131,36 @@ SS.blast = (function () {
131
131
  });
132
132
  }
133
133
 
134
- /* */
134
+ /**
135
+ * Triggers 'blast_method_changed' event if BLAST algorithms that can be
136
+ * used for the user input have changed.
137
+ */
135
138
  var signal_blast_method_changed = function () {
136
- var method, tmp;
139
+ var method, _method;
137
140
 
138
141
  $('#blast').on('sequence_type_changed database_type_changed',
139
142
  function (event) {
140
- tmp = determine_blast_method();
143
+ _method = determine_blast_method();
141
144
 
142
- if (tmp != method) {
143
- method = tmp;
145
+ if (!_.isEqual(_method, method)) {
146
+ method = _method;
144
147
 
145
148
  //notify listeners
146
- $(this).trigger('blast_method_changed', [method]);
149
+ $(this).trigger('blast_method_changed', [method.slice()]);
147
150
  }
148
151
  });
149
152
  }
150
153
 
151
- /*
152
- Return a BLAST method to use for the selected database and input
153
- sequence type.
154
-
155
- database method
156
- ------------------------
157
- protein blastx
158
- protein blastp
159
- ------------------------
160
- nucleotide tblastx
161
- nucleotide tblastn
162
- nucleotide blastn
163
- */
154
+ /**
155
+ * Returns name of BLAST algorithms that can be used for the input query
156
+ * and selected database combination.
157
+ *
158
+ * Returns empty array if no BLAST algorithm is appropriate for the user
159
+ * input.
160
+ */
164
161
  var determine_blast_method = function () {
165
162
  if (!required_params_present()) {
166
- return
163
+ return [];
167
164
  }
168
165
 
169
166
  var database_type = type_of_databases();
@@ -190,6 +187,8 @@ SS.blast = (function () {
190
187
  return ['blastn', 'tblastx'];
191
188
  }
192
189
  }
190
+
191
+ return [];
193
192
  }
194
193
 
195
194
  /* public interface */
@@ -2,31 +2,46 @@
2
2
  (function( $ ){
3
3
  //disable an element
4
4
  $.fn.disable = function() {
5
- return this.attr('disabled', 'disabled');
5
+ return this.prop('disabled', true).addClass('disabled');
6
6
  };
7
7
  })( jQuery );
8
8
 
9
9
  (function( $ ){
10
10
  //enable an element
11
11
  $.fn.enable = function() {
12
- return this.removeAttr('disabled');
12
+ return this.prop('disabled', false).removeClass('disabled');
13
13
  };
14
14
  })( jQuery );
15
15
 
16
16
  (function( $ ){
17
17
  //uncheck an element
18
18
  $.fn.uncheck = function() {
19
- return this.removeAttr('checked');
19
+ return this.prop('checked', false);
20
20
  };
21
21
  })( jQuery );
22
22
 
23
23
  (function( $ ){
24
24
  //check an element
25
25
  $.fn.check = function() {
26
- return this.attr('checked', 'checked');
26
+ return this.prop('checked', true);
27
27
  };
28
28
  })( jQuery );
29
29
 
30
+ /**
31
+ * Wiggle an element.
32
+ *
33
+ * Used for wiggling BLAST button.
34
+ */
35
+ (function ($) {
36
+ $.fn.wiggle = function() {
37
+ this.finish().effect("bounce", {
38
+ direction: 'left',
39
+ distance: 24,
40
+ times: 4,
41
+ }, 250);
42
+ };
43
+ }(jQuery));
44
+
30
45
  (function( $ ){
31
46
  //(pre-)check the only active database checkbox
32
47
  $.onedb = function(selector) {
@@ -38,25 +53,12 @@
38
53
  };
39
54
  })( jQuery );
40
55
 
41
- (function( $ ){
42
- //highlight an element
43
- $.fn.highlight = function() {
44
- return this.addClass('focussed');
45
- };
46
- })( jQuery );
47
-
48
- (function( $ ){
49
- //unhighlight an element
50
- $.fn.unhighlight = function() {
51
- return this.removeClass('focussed');
52
- };
53
- })( jQuery );
54
-
55
56
  (function ($) {
56
57
  $.fn.poll = function () {
57
58
  var that, val, tmp;
58
59
 
59
60
  that = this;
61
+ val = that.val();
60
62
 
61
63
  (function ping () {
62
64
  tmp = that.val();
@@ -92,6 +94,9 @@ if (!SS) {
92
94
  //SS module
93
95
  (function () {
94
96
 
97
+ // Starts with >.
98
+ SS.FASTA_FORMAT = /^>/;
99
+
95
100
  SS.decorate = function (name) {
96
101
  return name.match(/(.?)(blast)(.?)/).slice(1).map(function (token, _) {
97
102
  if (token) {
@@ -103,28 +108,437 @@ if (!SS) {
103
108
  }
104
109
  }
105
110
  }).join('');
106
- }
111
+ };
112
+
113
+ SS.generateGraphicalOverview = function () {
114
+ $("[data-graphit='overview']").each(function () {
115
+ var $this = $(this);
116
+ var $graphDiv = $('<div/>').addClass('graphical-overview');
117
+ $this.children().eq(1).children().eq(0).before($graphDiv);
118
+
119
+ $.graphIt($this, $graphDiv, 0, 20);
120
+ });
121
+ };
122
+
123
+ SS.updateDownloadFastaOfAllLink = function () {
124
+ var num_hits = $('.hitn').length;
125
+
126
+ var $a = $('.download-fasta-of-all');
127
+ if (num_hits >= 1 && num_hits <= 30) {
128
+ var sequence_ids = $('.hitn :checkbox').map(function() {
129
+ return this.value;
130
+ }).get();
131
+ $a
132
+ .enable()
133
+ .attr('href', SS.generateURI(sequence_ids, $a.data().databases))
134
+ .tooltip('destroy')
135
+ .tooltip({
136
+ placement: 'left',
137
+ title: num_hits + " hit(s)."
138
+ });
139
+ return;
140
+ }
141
+
142
+ if (num_hits == 0) {
143
+ $a
144
+ .tooltip('destroy')
145
+ .tooltip({
146
+ placement: 'left',
147
+ title: "No hit to download."
148
+ });
149
+ }
150
+
151
+ if (num_hits > 30) {
152
+ $a
153
+ .tooltip('destroy')
154
+ .tooltip({
155
+ placement: 'left',
156
+ title: "Can't download more than 30 hits."
157
+ });
158
+ }
159
+ $a.disable();
160
+ };
161
+
162
+ /* Update the FASTA downloader button's state appropriately.
163
+ *
164
+ * When more than 30 hits are obtained, the link is disabled.
165
+ * When no hits are obtained, the link is not present at all.
166
+ */
167
+ SS.updateDownloadFastaOfSelectedLink = function () {
168
+ var num_checked = $('.hitn :checkbox:checked').length;
169
+
170
+ var $a = $('.download-fasta-of-selected');
171
+ var $n = $a.find('span');
172
+
173
+ if (num_checked >= 1 && num_checked <= 30) {
174
+ var sequence_ids = $('.hitn :checkbox:checked').map(function () {
175
+ return this.value;
176
+ }).get();
177
+
178
+ $a
179
+ .enable()
180
+ .attr('href', SS.generateURI(sequence_ids, $a.data().databases))
181
+ .tooltip('destroy')
182
+ .tooltip({
183
+ placement: 'left',
184
+ title: num_checked + " hit(s) selected."
185
+ })
186
+ .find('span').html(num_checked);
187
+ return;
188
+ }
189
+
190
+ if (num_checked == 0) {
191
+ $n.empty();
192
+ $a
193
+ .tooltip('destroy')
194
+ .tooltip({
195
+ placement: 'left',
196
+ title: "No hit selected."
197
+ });
198
+ }
199
+
200
+ if (num_checked > 30) {
201
+ $a
202
+ .tooltip('destroy')
203
+ .tooltip({
204
+ placement: 'left',
205
+ title: "Can't download more than 30 hits."
206
+ });
207
+ }
208
+
209
+ $a
210
+ .disable()
211
+ .removeAttr('href');
212
+ };
213
+
214
+ SS.updateSequenceViewerLinks = function () {
215
+ var MAX_LENGTH = 10000;
216
+
217
+ $('.view-sequence').each(function () {
218
+ var $this = $(this);
219
+ var $hitn = $this.closest('.hitn');
220
+ if ($hitn.data().hitLen > MAX_LENGTH) {
221
+ $this
222
+ .disable()
223
+ .removeAttr('href')
224
+ .tooltip({
225
+ title: 'Sequence too long to show. Please view it ' +
226
+ 'locally after download.'
227
+ });
228
+ }
229
+ });
230
+ };
231
+
232
+ SS.setupTooltipsForPosLabels = function () {
233
+ $('.pos-label').each(function () {
234
+ $(this).tooltip({
235
+ container: 'body',
236
+ placement: 'right',
237
+ });
238
+ });
239
+ };
240
+
241
+ SS.generateURI = function (sequence_ids, database_ids) {
242
+ // Encode URIs against strange characters in sequence ids.
243
+ sequence_ids = encodeURIComponent(sequence_ids.join(' '));
244
+ database_ids = encodeURIComponent(database_ids);
245
+
246
+ var url = "get_sequence/?sequence_ids=" + sequence_ids +
247
+ "&database_ids=" + database_ids + '&download=fasta';
248
+
249
+ return url;
250
+ },
251
+
252
+ SS.showErrorModal = function (jqXHR, beforeShow) {
253
+ setTimeout(function () {
254
+ beforeShow();
255
+ if (jqXHR.responseText) {
256
+ $("#error").html(jqXHR.responseText).modal();
257
+ }
258
+ else {
259
+ $("#error-no-response").modal();
260
+ }
261
+ }, 500);
262
+ },
263
+
264
+ SS.init = function () {
265
+ this.$sequence = $('#sequence');
266
+ this.$sequenceFile = $('#sequence-file');
267
+ this.$sequenceControls = $('.sequence-controls');
268
+
269
+ this.$sequence.poll();
107
270
 
108
- /*
109
- ask each module to initialize itself
110
- */
111
- SS.main = function () {
112
271
  SS.blast.init();
113
- }
272
+ };
273
+
114
274
  }()); //end SS module
115
275
 
116
- $(document).ready(function(){
117
- // poll the sequence textbox for a change in user input
118
- $('#sequence').poll();
276
+ /**
277
+ * Highlight hit div corresponding to given checkbox and update bulk download
278
+ * link.
279
+ */
280
+ SS.selectHit = function (checkbox) {
281
+ if (!checkbox || !checkbox.value) return;
282
+
283
+ var $hitn = $('.hitn[data-hit-def="' + checkbox.value + '"]');
284
+
285
+ // Highlight selected hit and sync checkboxes if sequence viewer is open.
286
+ if(checkbox.checked) {
287
+ $hitn
288
+ .addClass('glow')
289
+ .find(":checkbox").not(checkbox).check();
290
+ } else {
291
+ $hitn
292
+ .removeClass('glow')
293
+ .find(":checkbox").not(checkbox).uncheck();
294
+ }
295
+
296
+ this.updateDownloadFastaOfSelectedLink();
297
+ };
119
298
 
120
- // start SequenceServer's event loop
121
- SS.main();
299
+ SS.showSequenceViewer = (function () {
300
+
301
+ var $viewer = $('#sequence-viewer');
302
+ var $spinner = $('.spinner', $viewer);
303
+ var $viewerBody = $('.modal-body', $viewer);
304
+ var $viewerFooter = $('.modal-footer', $viewer);
305
+
306
+ var widgetClass = 'biojs-vis-sequence';
307
+
308
+ var initViewer = function ($clicked) {
309
+ $viewerBody.empty();
310
+ $viewerFooter.empty();
311
+
312
+ initFooter($clicked);
313
+ $spinner.show();
314
+ $viewer.modal('show');
315
+ };
316
+
317
+ var initFooter = function ($clicked) {
318
+ // Generate links in the footer.
319
+ var links = $clicked.parent().clone();
320
+ var viewSequenceLink = links.find('.view-sequence');
321
+ var nextSeparator = $(viewSequenceLink[0].nextSibling);
322
+ viewSequenceLink.remove();
323
+ nextSeparator.remove();
324
+ $viewerFooter.empty().append(links);
325
+ };
326
+
327
+ var showSequences = function (sequence_ids, sequences, databases) {
328
+ // Inform user if number of sequences retrieved is more or less than requested for.
329
+ if (sequence_ids.length != sequences.length) {
330
+ showLessOrMoreSequencesRetrievedMessage(sequence_ids, sequences, databases);
331
+ }
332
+
333
+ // Render sequence.
334
+ sequences.forEach(showSequence);
335
+ };
336
+
337
+ var showSequence = function (sequence) {
338
+ // generate html template
339
+ var header = sequence.id + "<small>&nbsp;" + sequence.title + "</small>";
340
+ var widgetId = widgetClass + (new Date().getUTCMilliseconds());
341
+
342
+ $viewerBody
343
+ .append(
344
+ $('<div/>')
345
+ .addClass('fastan')
346
+ .append(
347
+ $('<div/>')
348
+ .addClass('page-header')
349
+ .append(
350
+ $('<h4>')
351
+ .html(header)
352
+ ),
353
+ $('<div>')
354
+ .attr('id', widgetId)
355
+ .addClass(widgetClass)
356
+ .addClass('page-content')
357
+ )
358
+ );
359
+
360
+ // attach BioJS sequence viewer
361
+ var widget = new Sequence({
362
+ sequence: sequence.value,
363
+ target: widgetId,
364
+ format: 'CODATA',
365
+ columns: {
366
+ size: 35
367
+ },
368
+ formatOptions: {
369
+ title: false,
370
+ footer: false
371
+ }
372
+ });
373
+ widget.hideFormatSelector();
374
+ };
375
+
376
+ var showLessOrMoreSequencesRetrievedMessage = function (sequence_ids, sequences, databases) {
377
+ $viewerBody
378
+ .append(
379
+ $('<h4/>')
380
+ .html("ERROR: incorrect number of sequences found."),
381
+ $('<p/>')
382
+ .html('You requested ' + sequence_ids.length +
383
+ ' sequence(s) with the following identifiers: <br>' +
384
+ '<code>' + sequence_ids.join(', ') + '</code> <br>' +
385
+ 'from the following databases: <br>' +
386
+ '<code>' + databases.join(', ') + '</code> <br>' +
387
+ 'but we found ' + sequences.length + ' sequence(s).'),
388
+ $('<p/>')
389
+ .html('This is likley due to a problem with how databases ' +
390
+ 'are formatted.<strong> Please share this text with ' +
391
+ 'the person managing this website so that the issue ' +
392
+ 'can be resolved.'),
393
+ $('<p/>')
394
+ .html('If any sequences were retrieved, you ' +
395
+ 'can find them below (but some may be incorrect, so ' +
396
+ 'be careful!)')
397
+ );
398
+ };
399
+
400
+ return function (clicked) {
401
+ var $clicked = $(clicked);
402
+ initViewer($clicked);
403
+
404
+ var url = $clicked.attr('href');
405
+ $.getJSON(url)
406
+ .done(function (response) {
407
+ showSequences(response.sequence_ids, response.sequences, response.databases);
408
+ $spinner.hide();
409
+ })
410
+ .fail(function (jqXHR, status, error) {
411
+ SS.showErrorModal(jqXHR, function () {
412
+ $viewer.modal('hide');
413
+ });
414
+ });
415
+ };
416
+ }());
417
+
418
+ $(document).ready(function(){
419
+ SS.init();
122
420
 
123
421
  var notification_timeout;
124
422
 
423
+ // drag-and-drop code
424
+ var tgtMarker = $('.dnd-overlay');
425
+ var $sequence = $('#sequence');
426
+
427
+ var dndError = function (id) {
428
+ $('.dnd-error').hide();
429
+ $('#' + id + '-notification').show();
430
+ tgtMarker.effect('fade', 2500);
431
+ };
432
+
433
+ $(document)
434
+ .on('dragenter', function (evt) {
435
+ // Based on http://stackoverflow.com/a/8494918/1205465.
436
+ // Contrary to what the above link says, the snippet below can't
437
+ // distinguish directories from files. We handle that on drop.
438
+ var dt = evt.originalEvent.dataTransfer;
439
+ var isFile = dt.types && ((dt.types.indexOf && // Chrome and Safari
440
+ dt.types.indexOf('Files') != -1) ||
441
+ (dt.types.contains && // Firefox
442
+ dt.types.contains('application/x-moz-file')));
443
+
444
+ if (!isFile) { return; }
445
+
446
+ $('.dnd-error').hide();
447
+ tgtMarker.stop(true, true);
448
+ tgtMarker.show();
449
+ dt.effectAllowed = 'copy';
450
+ if ($sequence.val() === '') {
451
+ $('.dnd-overlay-overwrite').hide();
452
+ $('.dnd-overlay-drop').show('drop', {direction: 'down'}, 'fast');
453
+ }
454
+ else {
455
+ $('.dnd-overlay-drop').hide();
456
+ $('.dnd-overlay-overwrite').show('drop', {direction: 'down'}, 'fast');
457
+ }
458
+ })
459
+ .on('dragleave', '.dnd-overlay', function (evt) {
460
+ tgtMarker.hide();
461
+ $('.dnd-overlay-drop').hide();
462
+ $('.dnd-overlay-overwrite').hide();
463
+ })
464
+ .on('dragover', '.dnd-overlay', function (evt) {
465
+ evt.originalEvent.dataTransfer.dropEffect = 'copy';
466
+ evt.preventDefault();
467
+ })
468
+ .on('drop', '.dnd-overlay', function (evt) {
469
+ evt.preventDefault();
470
+ evt.stopPropagation();
471
+
472
+ var textarea = $('#sequence');
473
+ var indicator = $('#sequence-file');
474
+ textarea.focus();
475
+
476
+ var files = evt.originalEvent.dataTransfer.files;
477
+ if (files.length > 1) {
478
+ dndError('dnd-multi');
479
+ return;
480
+ }
481
+
482
+ var file = files[0];
483
+ if (file.size > 10 * 1048576) {
484
+ dndError('dnd-large-file');
485
+ return;
486
+ }
487
+
488
+ var reader = new FileReader();
489
+ reader.onload = function (e) {
490
+ var content = e.target.result;
491
+ if (SS.FASTA_FORMAT.test(content)) {
492
+ textarea.val(content);
493
+ indicator.text(file.name);
494
+ tgtMarker.hide();
495
+ } else {
496
+ // apparently not FASTA
497
+ dndError('dnd-format');
498
+ }
499
+ };
500
+ reader.onerror = function (e) {
501
+ // Couldn't read. Means dropped stuff wasn't FASTA file.
502
+ dndError('dnd-format');
503
+ }
504
+ reader.readAsText(file);
505
+ });
506
+ // end drag-and-drop
507
+
508
+ SS.$sequence.change(function () {
509
+ if (SS.$sequence.val()) {
510
+ // Calculation below is based on -
511
+ // http://chris-spittles.co.uk/jquery-calculate-scrollbar-width/
512
+ var sequenceControlsRight = SS.$sequence[0].offsetWidth -
513
+ SS.$sequence[0].clientWidth;
514
+ SS.$sequenceControls.css('right', sequenceControlsRight + 17);
515
+ SS.$sequenceControls.removeClass('hidden');
516
+ }
517
+ else {
518
+ SS.$sequenceFile.empty();
519
+ SS.$sequenceControls.addClass('hidden');
520
+ SS.$sequence.parent().removeClass('has-error');
521
+ }
522
+ });
523
+
524
+ // Handle clearing query sequences(s) when x button is pressed.
525
+ $('#btn-sequence-clear').click(function (e) {
526
+ $('#sequence').val("").focus();
527
+ })
528
+
529
+ // pre-select if only on db
530
+ $.onedb();
531
+
532
+ // Handles the form submission when Ctrl+Enter is pressed anywhere on page
533
+ $(document).bind("keydown", function (e) {
534
+ if (e.ctrlKey && e.keyCode === 13 && !$('#method').is(':disabled')) {
535
+ $('#method').trigger('submit');
536
+ }
537
+ });
538
+
125
539
  $('#sequence').on('sequence_type_changed', function (event, type) {
126
540
  clearTimeout(notification_timeout);
127
- $(this).parent('.control-group').removeClass('error');
541
+ $(this).parent().removeClass('has-error');
128
542
  $('.notifications .active').hide().removeClass('active');
129
543
 
130
544
  if (type) {
@@ -135,7 +549,7 @@ $(document).ready(function(){
135
549
  }, 5000);
136
550
 
137
551
  if (type === 'mixed') {
138
- $(this).parent('.control-group').addClass('error');
552
+ $(this).parent().addClass('has-error');
139
553
  }
140
554
  }
141
555
  });
@@ -148,70 +562,113 @@ $(document).ready(function(){
148
562
  switch (type) {
149
563
  case 'protein':
150
564
  $('.databases.nucleotide input:checkbox').uncheck().disable();
565
+ $('.databases.nucleotide .checkbox').addClass('disabled');
151
566
  break;
152
567
  case 'nucleotide':
153
568
  $('.databases.protein input:checkbox').uncheck().disable();
569
+ $('.databases.protein .checkbox').addClass('disabled');
154
570
  break;
155
571
  default:
156
572
  $('.databases input:checkbox').enable();
573
+ $('.databases .checkbox').removeClass('disabled');
157
574
  break;
158
575
  }
159
576
  });
160
577
 
161
- $('form').on('blast_method_changed', function (event, methods){
578
+ $('form').on('blast_method_changed', function (event, methods) {
162
579
  // reset
163
- $('#methods .dropdown-menu').html('');
164
- $('#method').disable().val('').html('blast');
165
- $('#methods').removeClass('btn-group').children('.dropdown-toggle').hide();
580
+ $('#method')
581
+ .disable().val('').html('blast')
582
+ .removeClass('col-md-11').addClass('col-md-12');
166
583
 
167
- if (methods) {
584
+ $('#methods')
585
+ .children().not('#method').remove();
586
+
587
+ // set
588
+ if (methods.length > 0) {
168
589
  var method = methods.shift();
169
590
 
170
- $('#method').enable().val(method).html(SS.decorate(method));
591
+ $('#method')
592
+ .enable().val(method).html(SS.decorate(method));
171
593
 
172
594
  if (methods.length >=1) {
173
- $('#methods').addClass('btn-group').
174
- children('.dropdown-toggle').show();
175
-
176
- var methods_list = $.map(methods, function (method, _) {
177
- return "<li>" + SS.decorate(method) + "</li>";
178
- }).join('');
179
-
180
- $('#methods .dropdown-menu').html(methods_list);
595
+ $('#methods')
596
+ .append
597
+ (
598
+ $('<button/>')
599
+ .attr('type', 'button')
600
+ .addClass("btn btn-primary dropdown-toggle col-md-1")
601
+ .attr('data-toggle', 'dropdown')
602
+ .append
603
+ (
604
+ $('<span/>')
605
+ .addClass('caret')
606
+ ),
607
+ $('<ul/>')
608
+ .addClass('dropdown-menu')
609
+ .append
610
+ (
611
+ $.map(methods, function (method) {
612
+ return $('<li/>').html(SS.decorate(method));
613
+ })
614
+ )
615
+ );
616
+
617
+ $('#method')
618
+ .removeClass('col-md-12').addClass('col-md-11');
181
619
  }
182
620
 
183
621
  // jiggle
184
- $("#methods").effect("bounce", { times:5, direction: 'left', distance: 12 }, 120);
622
+ $("#methods").wiggle();
185
623
  }
186
624
  });
187
625
 
188
- $('#methods .dropdown-menu').click(function (event) {
189
- // The list of possible blast methods is dynamically generated. So we
190
- // leverage event bubbling to trap 'click' event on the list items.
191
- var clicked = $(event.target);
626
+ // The list of possible blast methods is dynamically generated. So we
627
+ // leverage event bubbling and delegation to trap 'click' event on the list items.
628
+ // Please see : http://api.jquery.com/on/#direct-and-delegated-events
629
+ $(document).on("click", "#methods .dropdown-menu li", function(event) {
630
+ var clicked = $(this);
192
631
  var mbutton = $('#method');
193
-
194
632
  var old_method = mbutton.text();
195
633
  var new_method = clicked.text();
196
634
 
197
- //swap
635
+ // swap
198
636
  clicked.html(SS.decorate(old_method));
199
637
  mbutton.val(new_method).html(SS.decorate(new_method));
200
638
 
201
639
  // jiggle
202
- $("#methods").effect("bounce", { times:5, direction: 'left', distance: 12 }, 120);
640
+ $("#methods").wiggle();
203
641
  });
204
642
 
205
- $("input#advanced").enablePlaceholder({"withPlaceholderClass": "greytext"});
206
- $("textarea#sequence").enablePlaceholder({"withPlaceholderClass": "greytext"});
643
+ // HACK to allow users to select names from hit headers
644
+ $('.result').on('mousedown', ".hitn > .page-header > h4", function (event) {
645
+ var $this = $(this);
646
+ $this.on('mouseup mousemove', function handler(event) {
647
+ if (event.type === 'mouseup') {
648
+ // user wants to toggle
649
+ $this.attr('data-toggle', 'collapse');
650
+ $this.find('.fa-chevron-down').toggleClass('fa-rotate-270');
651
+ } else {
652
+ // user wants to select
653
+ $this.attr('data-toggle', '');
654
+ }
655
+ $this.off('mouseup mousemove', handler);
656
+ });
657
+ });
207
658
 
208
- $('.advanced pre').hover(function () {
209
- $(this).addClass('hover-focus');
210
- },
211
- function () {
212
- $(this).removeClass('hover-focus');
659
+ $('.result').on('click', '.view-sequence', function (event) {
660
+ event.preventDefault();
661
+ event.stopPropagation();
662
+ if (!event.target.disabled)
663
+ SS.showSequenceViewer(event.target);
664
+ });
665
+
666
+ $(document).on('change', '.hit-links :checkbox', function (event) {
667
+ event.stopPropagation();
668
+ SS.selectHit(this);
213
669
  });
214
670
 
671
+
215
672
  $('#blast').submit(function(){
216
673
  //parse AJAX URL
217
674
  var action = $(this).attr('action');
@@ -222,14 +679,8 @@ $(document).ready(function(){
222
679
  // reset hash so we can always _jump_ back to result
223
680
  location.hash = '';
224
681
 
225
- // display a modal window and attach an activity spinner to it
682
+ // show activity spinner
226
683
  $('#spinner').modal();
227
- $('#spinner > div').activity({
228
- segments: 8,
229
- length: 40,
230
- width: 16,
231
- speed: 1.8
232
- });
233
684
 
234
685
  // BLAST now
235
686
  var data = ($(this).serialize() + '&method=' + $('#method').val());
@@ -238,67 +689,40 @@ $(document).ready(function(){
238
689
  // BLASTed successfully
239
690
 
240
691
  // display the result
241
- $('.results').show();
242
- $('#result').html(data);
243
-
244
- $('#blast').addClass('detached-bottom');
245
- $('#underbar').addClass('detached-top');
692
+ $('.result').html(data).show();
693
+
694
+ // affix sidebar
695
+ var $sidebar = $('.sidebar');
696
+ if ($sidebar.length !== 0) {
697
+ $sidebar.affix({
698
+ offset: {
699
+ top: $sidebar.offset().top
700
+ }
701
+ })
702
+ .width($sidebar.width());
703
+ }
246
704
 
247
705
  //jump to the results
248
706
  location.hash = hash;
249
707
 
250
- $('.resultn').
251
- scrollspy({
252
- approach: screen.height / 4
253
- }).
254
- on('enter.scrollspy', function () {
255
- var id = $(this).attr('id');
256
- $(this).highlight();
257
- $('.index').find('a[href="#' + id + '"]').parent().highlight();
258
-
259
- return false;
260
- }).
261
- on('leave.scrollspy', function () {
262
- var id = $(this).attr('id');
263
- $(this).unhighlight();
264
- $('.index').find('a[href="#' + id + '"]').parent().unhighlight();
265
-
266
- return false;
267
- });
268
- }).
269
- fail(function (jqXHR, status, error) {
270
- //alert user
271
- $("#error-type").text(error);
272
- if(jqXHR.responseText == ''){
273
- $("#error-message").text('Is the SequenceServer running? The JQuery status was \''+status+'\', and the JQuery error was \''+error+'\'.');
274
- } else {
275
- $("#error-message").text(jqXHR.responseText);
276
- }
277
- $("#error").modal();
278
- }).
279
- always(function () {
280
- // BLAST complete (succefully or otherwise)
708
+ SS.generateGraphicalOverview();
709
+
710
+ SS.updateDownloadFastaOfAllLink();
711
+ SS.updateDownloadFastaOfSelectedLink();
712
+ SS.updateSequenceViewerLinks();
713
+ SS.setupTooltipsForPosLabels();
714
+
715
+ $('body').scrollspy({target: '.sidebar'});
281
716
 
282
- // remove progress notification
283
- $('#spinner > div').activity(false);
284
717
  $('#spinner').modal('hide');
718
+
719
+ }).
720
+ fail(function (jqXHR, status, error) {
721
+ SS.showErrorModal(jqXHR, function () {
722
+ $('#spinner').modal('hide');
723
+ });
285
724
  });
286
725
 
287
726
  return false;
288
727
  });
289
-
290
- (function (store) {
291
- try {
292
- var visits = store.get('visits') || 0;
293
- visits = visits + 1;
294
- if (visits <= 10) {
295
- store.set('visits', visits);
296
-
297
- if (visits === 10) {
298
- $('#social').modal();
299
- }
300
- }
301
- }
302
- catch (e) {};
303
- }(store));
304
728
  });