quorum 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +35 -28
- data/HISTORY.md +4 -0
- data/app/assets/javascripts/quorum/application.js +1 -0
- data/app/assets/javascripts/quorum/jobs.js +5 -345
- data/app/assets/javascripts/quorum/quorum.js +275 -0
- data/app/assets/javascripts/quorum/template_settings.js +8 -0
- data/app/assets/javascripts/quorum/utilities.js +12 -0
- data/app/assets/stylesheets/quorum/application.css +0 -4
- data/app/assets/stylesheets/quorum/autohint.css +4 -0
- data/app/controllers/quorum/jobs_controller.rb +9 -9
- data/app/views/quorum/jobs/form/_blastn_form.html.erb +62 -62
- data/app/views/quorum/jobs/form/_blastp_form.html.erb +62 -62
- data/app/views/quorum/jobs/form/_blastx_form.html.erb +62 -62
- data/app/views/quorum/jobs/form/_tblastn_form.html.erb +62 -62
- data/app/views/quorum/jobs/show.html.erb +1 -1
- data/app/views/quorum/jobs/templates/_blast_detailed_report_template.html.erb +22 -22
- data/app/views/quorum/jobs/templates/_blast_template.html.erb +29 -29
- data/app/views/shared/_error_messages.html.erb +8 -8
- data/lib/quorum/version.rb +1 -1
- data/lib/tasks/jasmine.rake +8 -0
- data/quorum.gemspec +1 -0
- data/spec/data/seqs_not_fa.txt +16 -16
- data/spec/javascripts/fixtures/formatted_sequence.html +6 -0
- data/spec/javascripts/fixtures/quorum_search_form.html +461 -0
- data/spec/javascripts/fixtures/quorum_tabs.html +10 -0
- data/spec/javascripts/helpers/jasmine-jquery.js +288 -0
- data/spec/javascripts/jobs_spec.js +99 -0
- data/spec/javascripts/jquery/jquery-ui.min.js +791 -0
- data/spec/javascripts/jquery/jquery.min.js +4 -0
- data/spec/javascripts/jquery/jquery_ujs.js +373 -0
- data/spec/javascripts/quorum_spec.js +106 -0
- data/spec/javascripts/string_spec.js +18 -0
- data/spec/javascripts/support/jasmine.yml +84 -0
- data/spec/javascripts/support/jasmine_config.rb +23 -0
- data/spec/javascripts/support/jasmine_runner.rb +33 -0
- data/spec/requests/jobs_spec.rb +34 -33
- data/vendor/assets/javascripts/jquery.autohint.js +87 -0
- metadata +62 -26
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
quorum (0.
|
4
|
+
quorum (0.3.0)
|
5
5
|
bio-blastxmlparser (~> 1.0.1)
|
6
6
|
jquery-rails
|
7
7
|
net-ssh (~> 2.3.0)
|
@@ -12,31 +12,31 @@ PATH
|
|
12
12
|
GEM
|
13
13
|
remote: http://rubygems.org/
|
14
14
|
specs:
|
15
|
-
actionmailer (3.2.
|
16
|
-
actionpack (= 3.2.
|
15
|
+
actionmailer (3.2.1)
|
16
|
+
actionpack (= 3.2.1)
|
17
17
|
mail (~> 2.4.0)
|
18
|
-
actionpack (3.2.
|
19
|
-
activemodel (= 3.2.
|
20
|
-
activesupport (= 3.2.
|
18
|
+
actionpack (3.2.1)
|
19
|
+
activemodel (= 3.2.1)
|
20
|
+
activesupport (= 3.2.1)
|
21
21
|
builder (~> 3.0.0)
|
22
22
|
erubis (~> 2.7.0)
|
23
|
-
journey (~> 1.0.
|
23
|
+
journey (~> 1.0.1)
|
24
24
|
rack (~> 1.4.0)
|
25
25
|
rack-cache (~> 1.1)
|
26
26
|
rack-test (~> 0.6.1)
|
27
27
|
sprockets (~> 2.1.2)
|
28
|
-
activemodel (3.2.
|
29
|
-
activesupport (= 3.2.
|
28
|
+
activemodel (3.2.1)
|
29
|
+
activesupport (= 3.2.1)
|
30
30
|
builder (~> 3.0.0)
|
31
|
-
activerecord (3.2.
|
32
|
-
activemodel (= 3.2.
|
33
|
-
activesupport (= 3.2.
|
31
|
+
activerecord (3.2.1)
|
32
|
+
activemodel (= 3.2.1)
|
33
|
+
activesupport (= 3.2.1)
|
34
34
|
arel (~> 3.0.0)
|
35
35
|
tzinfo (~> 0.3.29)
|
36
|
-
activeresource (3.2.
|
37
|
-
activemodel (= 3.2.
|
38
|
-
activesupport (= 3.2.
|
39
|
-
activesupport (3.2.
|
36
|
+
activeresource (3.2.1)
|
37
|
+
activemodel (= 3.2.1)
|
38
|
+
activesupport (= 3.2.1)
|
39
|
+
activesupport (3.2.1)
|
40
40
|
i18n (~> 0.6)
|
41
41
|
multi_json (~> 1.0)
|
42
42
|
arel (3.0.0)
|
@@ -66,7 +66,13 @@ GEM
|
|
66
66
|
ffi (1.0.11)
|
67
67
|
hike (1.2.1)
|
68
68
|
i18n (0.6.0)
|
69
|
-
|
69
|
+
jasmine (1.1.2)
|
70
|
+
jasmine-core (>= 1.1.0)
|
71
|
+
rack (>= 1.1)
|
72
|
+
rspec (>= 1.3.1)
|
73
|
+
selenium-webdriver (>= 0.1.3)
|
74
|
+
jasmine-core (1.1.0)
|
75
|
+
journey (1.0.1)
|
70
76
|
jquery-rails (2.0.0)
|
71
77
|
railties (>= 3.2.0.beta, < 5.0)
|
72
78
|
thor (~> 0.14)
|
@@ -91,17 +97,17 @@ GEM
|
|
91
97
|
rack
|
92
98
|
rack-test (0.6.1)
|
93
99
|
rack (>= 1.0)
|
94
|
-
rails (3.2.
|
95
|
-
actionmailer (= 3.2.
|
96
|
-
actionpack (= 3.2.
|
97
|
-
activerecord (= 3.2.
|
98
|
-
activeresource (= 3.2.
|
99
|
-
activesupport (= 3.2.
|
100
|
+
rails (3.2.1)
|
101
|
+
actionmailer (= 3.2.1)
|
102
|
+
actionpack (= 3.2.1)
|
103
|
+
activerecord (= 3.2.1)
|
104
|
+
activeresource (= 3.2.1)
|
105
|
+
activesupport (= 3.2.1)
|
100
106
|
bundler (~> 1.0)
|
101
|
-
railties (= 3.2.
|
102
|
-
railties (3.2.
|
103
|
-
actionpack (= 3.2.
|
104
|
-
activesupport (= 3.2.
|
107
|
+
railties (= 3.2.1)
|
108
|
+
railties (3.2.1)
|
109
|
+
actionpack (= 3.2.1)
|
110
|
+
activesupport (= 3.2.1)
|
105
111
|
rack-ssl (~> 1.3.2)
|
106
112
|
rake (>= 0.8.7)
|
107
113
|
rdoc (~> 3.4)
|
@@ -139,7 +145,7 @@ GEM
|
|
139
145
|
railties (>= 3.0)
|
140
146
|
rspec (~> 2.8.0)
|
141
147
|
rubyzip (0.9.5)
|
142
|
-
selenium-webdriver (2.
|
148
|
+
selenium-webdriver (2.18.0)
|
143
149
|
childprocess (>= 0.2.5)
|
144
150
|
ffi (~> 1.0.9)
|
145
151
|
multi_json (~> 1.0.4)
|
@@ -170,6 +176,7 @@ DEPENDENCIES
|
|
170
176
|
capybara (~> 1.1)
|
171
177
|
database_cleaner (~> 0.6)
|
172
178
|
factory_girl_rails (~> 1.2)
|
179
|
+
jasmine (~> 1.1)
|
173
180
|
mysql2 (~> 0.3.11)
|
174
181
|
quorum!
|
175
182
|
resque_spec (~> 0.8)
|
data/HISTORY.md
CHANGED
@@ -1,72 +1,9 @@
|
|
1
|
-
//
|
2
|
-
// Mustache style Underscore.js templating.
|
3
|
-
//
|
4
|
-
_.templateSettings = {
|
5
|
-
evaluate: /\{\{(.+?)\}\}/g,
|
6
|
-
interpolate: /\{\{\=(.+?)\}\}/g
|
7
|
-
};
|
8
|
-
|
9
1
|
//
|
10
2
|
// jQuery
|
11
3
|
//---------------------------------------------------------------------------//
|
12
4
|
|
13
5
|
$(function() {
|
14
6
|
|
15
|
-
// jQuery Functions //
|
16
|
-
|
17
|
-
$.fn.extend({
|
18
|
-
// Add hints to the form elements.
|
19
|
-
addHints: function() {
|
20
|
-
$(this).each(function() {
|
21
|
-
if ($(this).attr('title') === '') {
|
22
|
-
return;
|
23
|
-
}
|
24
|
-
|
25
|
-
if ($(this).val() === '') {
|
26
|
-
$(this).val($(this).attr('title'));
|
27
|
-
|
28
|
-
if (!$(this).hasClass('auto-hint')) {
|
29
|
-
$(this).addClass('auto-hint');
|
30
|
-
}
|
31
|
-
} else {
|
32
|
-
$(this).removeClass('auto-hint');
|
33
|
-
}
|
34
|
-
});
|
35
|
-
},
|
36
|
-
|
37
|
-
// Remove hints on submit.
|
38
|
-
removeHintsOnSubmit: function() {
|
39
|
-
$(this).each(function() {
|
40
|
-
if ($(this).val() === $(this).attr('title')) {
|
41
|
-
$(this).val('');
|
42
|
-
}
|
43
|
-
});
|
44
|
-
},
|
45
|
-
|
46
|
-
// Remove hint and class on focus.
|
47
|
-
focusHint: function() {
|
48
|
-
$(this).focus(function() {
|
49
|
-
if ($(this).val() === $(this).attr('title')) {
|
50
|
-
$(this).val('');
|
51
|
-
$(this).removeClass('auto-hint');
|
52
|
-
}
|
53
|
-
});
|
54
|
-
},
|
55
|
-
|
56
|
-
// Retain value or add hint.
|
57
|
-
blurHint: function() {
|
58
|
-
$(this).blur(function() {
|
59
|
-
if ($(this).val() === '' && $(this).attr('title') !== '') {
|
60
|
-
$(this).val($(this).attr('title'));
|
61
|
-
$(this).addClass('auto-hint');
|
62
|
-
}
|
63
|
-
});
|
64
|
-
}
|
65
|
-
});
|
66
|
-
|
67
|
-
// End jQuery Functions //
|
68
|
-
|
69
|
-
|
70
7
|
// Hide Elements //
|
71
8
|
|
72
9
|
if (!$('#job_blastn_job_attributes_queue').is(':checked')) {
|
@@ -91,9 +28,7 @@ $(function() {
|
|
91
28
|
// Form //
|
92
29
|
|
93
30
|
var form = $('form :input.auto-hint');
|
94
|
-
form.
|
95
|
-
form.blurHint();
|
96
|
-
form.addHints();
|
31
|
+
form.autoHint();
|
97
32
|
|
98
33
|
// Toggle Algorithms //
|
99
34
|
|
@@ -121,21 +56,22 @@ $(function() {
|
|
121
56
|
'disabled', 'disabled'
|
122
57
|
);
|
123
58
|
|
124
|
-
form.
|
59
|
+
form.autoHint('removeHints');
|
125
60
|
});
|
126
61
|
|
127
62
|
// Reset form.
|
128
63
|
$('#quorum_job_reset').click(function() {
|
64
|
+
$('textarea').val('');
|
129
65
|
$('input:text').val('');
|
130
66
|
$('input:file').val('');
|
131
|
-
$('input:checkbox').
|
67
|
+
$('input:checkbox').prop('checked', false);
|
132
68
|
$('select').val('');
|
133
69
|
|
134
70
|
$('.toggle').each(function() {
|
135
71
|
$(this).hide();
|
136
72
|
});
|
137
73
|
|
138
|
-
form.addHints
|
74
|
+
form.autoHint('addHints');
|
139
75
|
});
|
140
76
|
|
141
77
|
// End Form //
|
@@ -150,279 +86,3 @@ $(function() {
|
|
150
86
|
|
151
87
|
});
|
152
88
|
|
153
|
-
|
154
|
-
//
|
155
|
-
// JavaScript Functions
|
156
|
-
//---------------------------------------------------------------------------//
|
157
|
-
|
158
|
-
//
|
159
|
-
// Poll quorum search results asynchronously and insert them into
|
160
|
-
// the DOM via #blast_template.
|
161
|
-
//
|
162
|
-
var pollResults = function(id, interval, algos) {
|
163
|
-
|
164
|
-
// Set the default poll interval to 5 seconds.
|
165
|
-
interval = interval || 5000;
|
166
|
-
|
167
|
-
// Algorithms
|
168
|
-
algos = algos || ["blastn", "blastx", "tblastn", "blastp"];
|
169
|
-
|
170
|
-
_.each(algos, function(a) {
|
171
|
-
$.getJSON(
|
172
|
-
'/quorum/jobs/' + id + '/get_quorum_search_results.json?algo=' + a,
|
173
|
-
function(data) {
|
174
|
-
if (data.length === 0) {
|
175
|
-
setTimeout(function() { pollResults(id, interval, [a]); }, interval);
|
176
|
-
} else {
|
177
|
-
$('#' + a + '-results').empty();
|
178
|
-
var temp = _.template(
|
179
|
-
$('#blast_template').html(), {
|
180
|
-
data: data,
|
181
|
-
algo: a
|
182
|
-
}
|
183
|
-
);
|
184
|
-
$('#' + a + '-results').html(temp);
|
185
|
-
return;
|
186
|
-
}
|
187
|
-
}
|
188
|
-
);
|
189
|
-
});
|
190
|
-
}
|
191
|
-
|
192
|
-
//
|
193
|
-
// Display jQuery UI modal box containing detailed report of all hits
|
194
|
-
// to the same query. After the modal box is inserted into the DOM,
|
195
|
-
// automatically scroll to the highlighted hit.
|
196
|
-
//
|
197
|
-
var viewDetailedReport = function(id, focus_id, query, algo) {
|
198
|
-
// Create the modal box.
|
199
|
-
$('#detailed_report_dialog').html(
|
200
|
-
"<p class='center'>" +
|
201
|
-
"Loading... <img src='/assets/quorum/loading.gif' alt='Loading'>" +
|
202
|
-
"</p>"
|
203
|
-
).dialog({
|
204
|
-
modal: true,
|
205
|
-
width: 850,
|
206
|
-
position: 'top'
|
207
|
-
});
|
208
|
-
|
209
|
-
$.getJSON(
|
210
|
-
'/quorum/jobs/' + id + '/get_quorum_search_results.json?algo=' + algo +
|
211
|
-
'&query=' + query,
|
212
|
-
function(data) {
|
213
|
-
var temp = _.template(
|
214
|
-
$('#detailed_report_template').html(), {
|
215
|
-
data: data,
|
216
|
-
query: query,
|
217
|
-
algo: algo
|
218
|
-
}
|
219
|
-
);
|
220
|
-
|
221
|
-
// Insert the detailed report data.
|
222
|
-
$('#detailed_report_dialog').empty().html(temp);
|
223
|
-
|
224
|
-
// Add tipsy to the sequence data.
|
225
|
-
$('a[rel=quorum-tipsy]').tipsy({ gravity: 's' });
|
226
|
-
|
227
|
-
// Highlight the selected id.
|
228
|
-
$('#' + focus_id).addClass("ui-state-highlight");
|
229
|
-
|
230
|
-
// Automatically scroll to the selected id.
|
231
|
-
autoScroll(focus_id, false);
|
232
|
-
}
|
233
|
-
);
|
234
|
-
}
|
235
|
-
|
236
|
-
//
|
237
|
-
// Helper to add title sequence position attribute for tipsy.
|
238
|
-
//
|
239
|
-
// If from > to decrement index; otherwise increment.
|
240
|
-
// If the algo is tblastn and type is hit OR algo is blastx and type is query,
|
241
|
-
// increment / decrement by 3; otherwise increment / decrement by 1.
|
242
|
-
//
|
243
|
-
var addBaseTitleIndex = function(bases, from, to, algo, type) {
|
244
|
-
var forward = true;
|
245
|
-
var value = 1;
|
246
|
-
var index = from;
|
247
|
-
|
248
|
-
if (from > to) {
|
249
|
-
forward = false;
|
250
|
-
}
|
251
|
-
|
252
|
-
// Set value to 3 for the below.
|
253
|
-
if ((type === "hit" && algo === "tblastn") ||
|
254
|
-
(type === "query" && algo === "blastx")) {
|
255
|
-
value = 3;
|
256
|
-
}
|
257
|
-
|
258
|
-
// Add tipsy to each base.
|
259
|
-
return _.map(bases.split(''), function(c) {
|
260
|
-
var str = "<a rel='quorum-tipsy' title=" + index + ">" + c + "</a>";
|
261
|
-
forward ? index += value : index -= value;
|
262
|
-
return str;
|
263
|
-
}).join('');
|
264
|
-
}
|
265
|
-
|
266
|
-
//
|
267
|
-
// Format sequence data for detailed report.
|
268
|
-
//
|
269
|
-
// If q_from > q_to or h_from > h_to, subtract by increment; otherwise add
|
270
|
-
// by increment.
|
271
|
-
//
|
272
|
-
// If algo is tblastn or blastx, multiple increment by 3.
|
273
|
-
//
|
274
|
-
var formatSequenceReport = function(qseq, midline, hseq, q_from, q_to, h_from, h_to, algo) {
|
275
|
-
var max = qseq.length; // max length
|
276
|
-
var increment = 60; // increment value
|
277
|
-
var s = 0; // start position
|
278
|
-
var e = increment; // end position
|
279
|
-
var seq = "\n"; // seq string to return
|
280
|
-
|
281
|
-
while(true) {
|
282
|
-
seq += "qseq " + addBaseTitleIndex(qseq.slice(s, e), q_from, q_to, algo, 'query') + "\n";
|
283
|
-
seq += " " + midline.slice(s, e) + "\n";
|
284
|
-
seq += "hseq " + addBaseTitleIndex(hseq.slice(s, e), h_from, h_to, algo, 'hit') + "\n\n";
|
285
|
-
|
286
|
-
if (e >= max) {
|
287
|
-
break;
|
288
|
-
}
|
289
|
-
|
290
|
-
s += increment;
|
291
|
-
e += increment;
|
292
|
-
|
293
|
-
// If the algorithm is blastx, increment * 3 only for qseq.
|
294
|
-
if (algo === "blastx") {
|
295
|
-
q_from < q_to ? q_from += (increment * 3) : q_from -= (increment * 3);
|
296
|
-
} else {
|
297
|
-
q_from < q_to ? q_from += increment : q_from -= increment;
|
298
|
-
}
|
299
|
-
|
300
|
-
// If the algorithm is tblastn, increment * 3 only for hseq.
|
301
|
-
if (algo === "tblastn") {
|
302
|
-
h_from < h_to ? h_from += (increment * 3) : h_from -= (increment * 3);
|
303
|
-
} else {
|
304
|
-
h_from < h_to ? h_from += increment : h_from -= increment;
|
305
|
-
}
|
306
|
-
}
|
307
|
-
return "<p class='small'>Alignment (Mouse over for positions):</p>" +
|
308
|
-
"<span class='small'><pre>" + seq + "</pre></span>";
|
309
|
-
}
|
310
|
-
|
311
|
-
//
|
312
|
-
// Format Query and Hit Strand.
|
313
|
-
//
|
314
|
-
// If query_frame or hit_frame < 0, print 'reverse'; print 'forward' otherwise.
|
315
|
-
//
|
316
|
-
var formatStrand = function(qstrand, hstrand) {
|
317
|
-
var q = "";
|
318
|
-
var h = "";
|
319
|
-
|
320
|
-
qstrand < 0 ? q = "reverse" : q = "forward";
|
321
|
-
hstrand < 0 ? h = "reverse" : h = "forward";
|
322
|
-
|
323
|
-
return q + " / " + h;
|
324
|
-
}
|
325
|
-
|
326
|
-
//
|
327
|
-
// Display links to Hsps in the same group.
|
328
|
-
//
|
329
|
-
var displayHspLinks = function(focus, group, data) {
|
330
|
-
if (group !== null) {
|
331
|
-
var str = "Related <a onclick=\"(openWindow(" +
|
332
|
-
"'http://www.ncbi.nlm.nih.gov/books/NBK62051/def-item/blast_glossary.HSP'," +
|
333
|
-
"'HSP', 800, 300))\">HSPs</a>: ";
|
334
|
-
|
335
|
-
var ids = _.map(group.split(","), function(i) { return parseInt(i); });
|
336
|
-
|
337
|
-
var selected = _(data).chain()
|
338
|
-
.reject(function(d) { return !_.include(ids, d.id); })
|
339
|
-
.sortBy(function(d) { return d.id; })
|
340
|
-
.value();
|
341
|
-
|
342
|
-
_.each(selected, function(e) {
|
343
|
-
if (e.id !== focus) {
|
344
|
-
str += "<a onclick='(autoScroll(" + e.id + ", true))'>" + e.hsp_num + "</a> ";
|
345
|
-
} else {
|
346
|
-
str += e.hsp_num + " ";
|
347
|
-
}
|
348
|
-
});
|
349
|
-
return str;
|
350
|
-
}
|
351
|
-
}
|
352
|
-
|
353
|
-
//
|
354
|
-
// Download Blast hit sequence.
|
355
|
-
//
|
356
|
-
var downloadSequence = function(id, algo_id, algo, el) {
|
357
|
-
$(el).html('Fetching sequence...');
|
358
|
-
|
359
|
-
$.getJSON(
|
360
|
-
"/quorum/jobs/" + id + "/get_quorum_blast_hit_sequence.json?algo_id=" +
|
361
|
-
algo_id + "&algo=" + algo,
|
362
|
-
function(data) {
|
363
|
-
getSequenceFile(id, data[0].meta_id, el);
|
364
|
-
}
|
365
|
-
);
|
366
|
-
}
|
367
|
-
|
368
|
-
//
|
369
|
-
// Poll application for Blast hit sequence.
|
370
|
-
//
|
371
|
-
var getSequenceFile = function(id, meta_id, el) {
|
372
|
-
var url = "/quorum/jobs/" + id +
|
373
|
-
"/send_quorum_blast_hit_sequence?meta_id=" + meta_id;
|
374
|
-
$.get(
|
375
|
-
url,
|
376
|
-
function(data) {
|
377
|
-
if (data.length === 0) {
|
378
|
-
setTimeout(function() { getSequenceFile(id, meta_id, el) }, 2500);
|
379
|
-
} else {
|
380
|
-
if (data.indexOf("error") !== -1) {
|
381
|
-
// Print error message.
|
382
|
-
$(el).addClass('ui-state-error').html(data);
|
383
|
-
} else {
|
384
|
-
// Force browser to download file via iframe.
|
385
|
-
$(el).addClass('ui-state-highlight').html('Sequence Downloaded Successfully');
|
386
|
-
$('.quorum_sequence_download').remove();
|
387
|
-
$('body').append('<iframe class="quorum_sequence_download"></iframe>');
|
388
|
-
$('.quorum_sequence_download').attr('src', url).hide();
|
389
|
-
}
|
390
|
-
}
|
391
|
-
}
|
392
|
-
);
|
393
|
-
}
|
394
|
-
|
395
|
-
//
|
396
|
-
// Autoscroll to given div id.
|
397
|
-
//
|
398
|
-
var autoScroll = function(id, highlight) {
|
399
|
-
$('html, body').animate({
|
400
|
-
scrollTop: $('#' + id).offset().top
|
401
|
-
}, 1000);
|
402
|
-
|
403
|
-
if (highlight) {
|
404
|
-
$('#' + id).effect("highlight", {}, 4000);
|
405
|
-
}
|
406
|
-
}
|
407
|
-
|
408
|
-
//
|
409
|
-
// Truncate string to length n using word boundary.
|
410
|
-
//
|
411
|
-
String.prototype.trunc = function(n) {
|
412
|
-
var longStr = this.length > n;
|
413
|
-
var str = longStr ? this.substr(0, n-1) : this;
|
414
|
-
|
415
|
-
longStr ? str.substr(0, str.lastIndexOf(' ')) : str;
|
416
|
-
return longStr ? str + '...' : str;
|
417
|
-
}
|
418
|
-
|
419
|
-
//
|
420
|
-
// Open URL in new window.
|
421
|
-
//
|
422
|
-
var openWindow = function(url, name, width, height) {
|
423
|
-
|
424
|
-
var windowSize = "width=" + width + ",height=" + height + ",scrollbars=yes";
|
425
|
-
|
426
|
-
window.open(url, name, windowSize);
|
427
|
-
}
|
428
|
-
|