fix test with new description field.
[sgn.git] / mason / search / markers / search.mas
blobd6cfb6e59ac8b80cea1839f252c1ec03dbbdf6da
1 <%doc>
3 =head1 NAME
5 /search/variants/index.mas - Unified Marker / Variants search page
7 =head1 DESCRIPTION
9 This page allows the user to search for mapped and genotyped markers using various 
10 filter criteria, such as the marker name and/or position.
12 =head1 AUTHOR
14 David Waring <djw64@cornell.edu>
16 =cut
18 </%doc>
20 <& /page/page_title.mas, title=>"Marker Search" &>
25 <!-- FILTER BY NAME -->
26 <&| /page/info_section.mas, title=>'Filter By Name', collapsible=>1, collapsed=>0 &>
27     <div class="well">
29         <!-- Marker Name -->
30         <div class="form-group">
31             <label class="col-sm-3 control-label">Marker Name: </label>
32             <div class="col-sm-4">
33                 <select id="marker-name-match" class="form-control">
34                     <option value="exactly">Exactly</option>
35                     <option value="contains">Contains</option>
36                     <option value="starts_with">Starts With</option>
37                     <option value="ends_with">Ends With</option>
38                 </select>
39             </div>
40             <div class="col-sm-5">
41                 <input id="marker-name" class="form-control" type="text" placeholder="Any Name" />
42             </div>
43         </div>
45         <br /><br />
47     </div>
48 </&>
52 <!-- FILTER BY POSITION -->
53 <&| /page/info_section.mas, title=>'Filter By Position', collapsible=>1, collapsed=>0 &>
54     <div class="well">
56         <!-- Reference Genome / Map -->
57         <div class="form-group">
58             <label class="col-sm-3 control-label">Reference Genome / Map: </label>
59             <div class="col-sm-9">
60                 <select id="marker-refmap" class="form-control" disabled>
61                     <option>Loading...</option>
62                 </select>
63             </div>
64         </div>
66         <br /><br />
68         <!-- Chromosome -->
69         <div class="form-group">
70             <label class="col-sm-3 control-label">Chromosome: </label>
71             <div class="col-sm-9">
72                 <select id="marker-chromosome" class="form-control" disabled>
73                     <option>Loading...</option>
74                 </select>
75             </div>
76         </div>
78         <br /><br />
80         <!-- Start / End Position -->
81         <div class="form-group">
82             <label class="col-sm-3 control-label">Start: </label>
83             <div class="col-sm-4">
84                 <div class="input-group">
85                     <input id="marker-position-start" class="form-control" type="text" disabled />
86                     <span class="input-group-addon marker-position-units"></span>
87                 </div>
88             </div>
89             <label class="col-sm-1 control-label">End: </label>
90             <div class="col-sm-4">
91                 <div class="input-group">
92                     <input id="marker-position-end" class="form-control" type="text" disabled />
93                     <span class="input-group-addon marker-position-units"></span>
94                 </div>
95             </div>
96         </div>
98         <br /><br />
99     
100     </div>
101 </&>
105 <!-- ADDITIONAL FILTERS -->
106 <&| /page/info_section.mas, title=>'Additional Filters', collapsible=>1, collapsed=>0 &>
107     <div class="well">
109         <!-- Species -->
110         <div class="form-group">
111             <label class="col-sm-3 control-label">Species: </label>
112             <div class="col-sm-9">
113                 <select id="marker-species" class="form-control" disabled>
114                     <option value=''>Loading...</option>
115                 </select>
116             </div>
117         </div>
119         <br /><br />
121         <!-- Protocol -->
122         <div class="form-group">
123             <label class="col-sm-3 control-label">Protocol: </label>
124             <div class="col-sm-9">
125                 <select id="marker-protocol" class="form-control" disabled>
126                     <option value=''>Loading...</option>
127                 </select>
128             </div>
129         </div>
131         <br /><br />
132     
133     </div>
134 </&>
137 <button class="btn btn-primary btn-block marker-search-button">Search</button>
141 <script type="text/javascript">
143 // Pre-selected values from query params
144 let SEL_MARKER_NAME, SEL_MARKER_NAME_MATCH;
145 let SEL_REFMAP, SEL_CHROM, SEL_START, SEL_END;
146 let SEL_SPECIES, SEL_PROTOCOL;
148 let REFMAPS = undefined;                // References and maps retrieved from the database, grouped by marker type
149 let CHROMOSOMES = undefined;            // Chromosomes retrieved from the database, grouped by marker type
150 let SPECIES = undefined;                // List of supported species, based on the refmaps
151 let PROTOCOLS = undefined;              // Protocols retrieved from the database, grouped by marker type
154 jQuery(document).ready(function() {
156     // Search Setup Functions
157     parseArgs();
158     getRefMaps();
159     getChromosomes();
160     getProtocols();
162     // Click / Change Listeners
163     jQuery('#marker-refmap').change(updateChromosomes).change(updatePositions).change(updateSpecies).change(updateProtocols);
164     jQuery('#marker-chromosome').change(updatePositions);
165     jQuery('#marker-species').change(updateRefMaps).change(updateProtocols);
166     jQuery('#marker-protocol').change(updateSpecies).change(updateRefMaps);
167     jQuery('.marker-search-button').click(search);
173  * Parse query arguments for initial search values
174  */
175 function parseArgs() {
176     const params = new URLSearchParams(window.location.search);
177     SEL_MARKER_NAME = _getValue("marker_name");
178     SEL_MARKER_NAME_MATCH = _getValue("marker_name_match");
179     SEL_REFMAP = _getValue("refmap");
180     SEL_SPECIES = _getValue("species");
181     SEL_CHROM = _getValue("chrom");
182     SEL_START = _getValue("start");
183     SEL_END = _getValue("end");
184     SEL_PROTOCOL = _getValue("protocol");
186     // Set the initial value of items that do need to be fetched from the DB
187     if ( SEL_MARKER_NAME ) jQuery("#marker-name").val(SEL_MARKER_NAME);
188     if ( SEL_MARKER_NAME_MATCH ) jQuery("#marker-name-match").val(SEL_MARKER_NAME_MATCH);
189     if ( SEL_REFMAP && SEL_CHROM && SEL_START ) jQuery("#marker-position-start").val(SEL_START);
190     if ( SEL_REFMAP && SEL_CHROM && SEL_END ) jQuery("#marker-position-end").val(SEL_END);
192     function _getValue(key, def) {
193         return params.has(key) ? params.get(key) : def;
194     }
199 // DATABASE QUERY FUNCTIONS
203  * Get the genotyped marker reference genomes and the mapped 
204  * marker maps to display as options for the position filter
205  * - call getReferences()
206  * - call getMaps()
207  * - call updateRefMaps()
208  */
209 function getRefMaps() {
210     let references = undefined;
211     let maps = undefined;
213     getReferences(function(_references) {
214         references = _references;
215         _finish();
216     });
217     getMaps(function(_maps) {
218         maps = _maps;
219         _finish();
220     });
222     function _finish() {
223         if ( references && maps ) {
224             REFMAPS = {
225                 genotyped: references,
226                 mapped: maps
227             };
228             updateRefMaps();
229         }
230     }
235  * Get the reference genomes and species of the genotyped markers
236  * @param {function} callback Callback function(references)
237  */
238 function getReferences(callback) {
239     jQuery.ajax({
240         type: 'GET',
241         url: '/ajax/markers/genotyped/reference_genomes',   
242         dataType: 'json',
243         success: function(data) {
244             return callback(data && data.reference_genomes ? data.reference_genomes : []);
245         },
246         error: function() {
247             alert("ERROR: Could not get reference genomes");
248         }
249     });
253  * Get the maps of the mapped markers
254  * @param {function} callback Callback function(maps)
255  */
256 function getMaps(callback) {
257     jQuery.ajax({
258         type: 'GET',
259         url: '/ajax/markers/mapped/maps',   
260         dataType: 'json',
261         success: function(data) {
262             return callback(data && data.maps ? data.maps : []);
263         },
264         error: function() {
265             alert("ERROR: Could not get maps");
266         }
267     });
272  * Get the chromosome names associated with genotyped and mapped markers
273  * - Call getGenotypedChromosomes()
274  * - Call getMappedChromosomes()
275  * - Set the CHROMOSOMES objects
276  * - Call updateChromosomes()
277  */
278 function getChromosomes() {
279     let genotyped = undefined;
280     let mapped = undefined;
282     getGenotypedChromosomes(function(_genotyped) {
283         genotyped = _genotyped;
284         _finish();
285     });
286     getMappedChromosomes(function(_mapped) {
287         mapped = _mapped;
288         _finish();
289     });
291     function _finish() {
292         if ( genotyped && mapped ) {
293             CHROMOSOMES = {
294                 genotyped: genotyped,
295                 mapped: mapped
296             }
297             updateChromosomes();
298         }
299     }
304  * Get the chromosome names associated with genotyped markers
305  * @param {function} callback Callback function(chromosomes)
306  */
307 function getGenotypedChromosomes(callback) {
308     jQuery.ajax({
309         type: 'GET',
310         url: '/ajax/markers/genotyped/chromosomes',   
311         dataType: 'json',
312         success: function(data) {
313             return callback(data && data.chromosomes ? data.chromosomes : []);
314         },
315         error: function() {
316             alert("ERROR: Could not get genotyped chromosomes");
317         }
318     });
323  * Get the chromosomes names associated with mapped markers
324  * @param {function} callback Callback function(chromosomes)
325  */
326 function getMappedChromosomes(callback) {
327     jQuery.ajax({
328         type: 'GET',
329         url: '/ajax/markers/mapped/chromosomes',   
330         dataType: 'json',
331         success: function(data) {
332             return callback(data && data.chromosomes ? data.chromosomes : []);
333         },
334         error: function() {
335             alert("ERROR: Could not get mapped chromosomes");
336         }
337     });
342  * Get the protocols associated with genotyped and mapped markers
343  * - Call getGenotypedProtocols()
344  * - Call getMappedProtocols()
345  * - Set the PROTOCOLS objects
346  * - Call updateProtocols()
347  */
348 function getProtocols() {
349     let genotyped = undefined;
350     let mapped = undefined;
352     getGenotypedProtocols(function(_genotyped) {
353         genotyped = _genotyped;
354         _finish();
355     });
356     getMappedProtocols(function(_mapped) {
357         mapped = _mapped;
358         _finish();
359     });
361     function _finish() {
362         if ( genotyped && mapped ) {
363             PROTOCOLS = {
364                 genotyped: genotyped,
365                 mapped: mapped
366             }
367             updateProtocols();
368         }
369     }
374  * Get the protocols associated with genotyped markers
375  * @param {function} callback Callback function(protocols)
376  */
377 function getGenotypedProtocols(callback) {
378     jQuery.ajax({
379         type: 'GET',
380         url: '/ajax/markers/genotyped/protocols',   
381         dataType: 'json',
382         success: function(data) {
383             return callback(data && data.protocols ? data.protocols : []);
384         },
385         error: function() {
386             alert("ERROR: Could not get genotyped protocols");
387         }
388     });
393  * Get the protocols associated with mapped markers
394  * @param {function} callback Callback function(protocols)
395  */
396 function getMappedProtocols(callback) {
397     jQuery.ajax({
398         type: 'GET',
399         url: '/ajax/markers/mapped/protocols',   
400         dataType: 'json',
401         success: function(data) {
402             return callback(data && data.protocols ? data.protocols : []);
403         },
404         error: function() {
405             alert("ERROR: Could not get mapped protocols");
406         }
407     });
413 // DISPLAY FUNCTIONS
417  * Display the reference genomes and maps in the position filter
418  * - Build the list of species used by the species select box
419  * - call updateChromosomes() and updateSpecies() when done
420  */
421 function updateRefMaps() {
422     let refmap = jQuery("#marker-refmap").val();
423     let protocol_refmap = jQuery("#marker-protocol option:selected").data("refmap");
424     let protocol_type = jQuery("#marker-protocol option:selected").data("type");
425     let species = jQuery("#marker-species").val();
427     if ( !REFMAPS || !REFMAPS.genotyped || !REFMAPS.mapped ) return;
428     let references = REFMAPS.genotyped;
429     let maps = REFMAPS.mapped;
430     SPECIES = [];
432     let ad = protocol_refmap && !Array.isArray(protocol_refmap) || (Array.isArray(protocol_refmap) && protocol_refmap.length === 1) ? 'disabled' : '';
433     let html = "<option value='' " + ad + ">Any Reference Genome / Map</option>";
435     // Add Genotype Reference Genomes
436     html += "<optgroup label='Reference Genomes of Genotyped Markers'>";
437     let gd = (protocol_type && protocol_type !== 'genotyped') || protocol_refmap ? 'disabled' : '';
438     html += "<option value='' data-type='genotyped' " + gd + ">Any Reference Genome</option>";
439     for ( let i = 0; i < references.length; i++ ) {
440         let d = (protocol_refmap && protocol_refmap !== references[i].reference_genome_name) 
441             || (protocol_type && protocol_type !== 'genotyped') 
442             || (species && references[i].species_name !== species) 
443             ? 'disabled' : '';
445         let s = d !== 'disabled' && refmap === references[i].reference_genome_name ? 'selected' : '';
446         html += "<option value='" + references[i].reference_genome_name + "' ";
447         html += "data-type='genotyped' data-units='bp' data-species='" + references[i].species_name + "' ";
448         html += d + " " + s + ">";
449         html += references[i].reference_genome_name + " (" + references[i].species_name + ")";
450         html += "</option>";
452         if ( !SPECIES.includes(references[i].species_name) ) SPECIES.push(references[i].species_name);
453     }
454     html += "</optgroup>";
456     // Add Maps
457     html += "<optgroup label='Maps of Mapped Markers'>";
458     let md = (protocol_type && protocol_type !== 'mapped') || protocol_refmap ? 'disabled' : '';
459     html += "<option value='' data-type='mapped' " + md + ">Any Map</option>";
460     for ( let i = 0; i < maps.length; i++ ) {
461         let d = (protocol_type === 'mapped' && protocol_refmap && !protocol_refmap.includes(maps[i].map_id+'')) 
462             || (protocol_type && protocol_type !== 'mapped') 
463             || (species && maps[i].species_name !== species) 
464             ? 'disabled' : '';
465         let s = d !== 'disabled' && refmap === maps[i].map_id+'' ? 'selected' : '';
466         html += "<option value='" + maps[i].map_id + "' "
467         html += "data-type='mapped' data-units='" + maps[i].map_units + "' data-species='" + maps[i].species_name + "' data-map='" + maps[i].map_name + "' ";
468         html += d + " " + s + ">";
469         html += maps[i].map_name + " (" + maps[i].species_name + ")";
470         html += "</option>";
472         if ( !SPECIES.includes(maps[i].species_name) ) SPECIES.push(maps[i].species_name);
473     }
474     html += "</optgroup>";
476     jQuery("#marker-refmap").html(html);
477     jQuery("#marker-refmap").attr('disabled', false);
479     // Set intial value, if not already set
480     if ( SEL_REFMAP ) {
481         jQuery("#marker-refmap").val(SEL_REFMAP);
482         SEL_REFMAP = undefined;
483     }
485     updateChromosomes();
486     updateSpecies();
491  * Update the list of chromosomes based on the selected ref / map
492  */
493 function updateChromosomes() {
494     let chrom = jQuery("#marker-chromosome").val();
495     let refmap = jQuery("#marker-refmap").val();
496     let type = jQuery("#marker-refmap option:selected").data("type");
497     let species = jQuery("#marker-refmap option:selected").data("species");
498     let map = jQuery("#marker-refmap option:selected").data("map");
500     if ( CHROMOSOMES ) {
501         let html = "";
502         let chroms = [];
503         if ( refmap ) {
504             if ( type === 'genotyped' ) {
505                 chroms = CHROMOSOMES.genotyped[species][refmap];
506             }
507             else if ( type === 'mapped' ) {
508                 chroms = CHROMOSOMES.mapped[map];
509             }
510         }
511         html += "<option value=''>Any Chromosome</option>";
512         for ( let i = 0; i < chroms.length; i++ ) {
513             let s = chrom && chroms[i] === chrom ? 'selected' : '';
514             html += "<option value='" + chroms[i] + "' " + s + ">" + chroms[i] + "</option>";
515         }
517         jQuery("#marker-chromosome").html(html);
518         jQuery("#marker-chromosome").attr("disabled", false);
520         // Set intial value, if not already set
521         if ( SEL_CHROM ) {
522             jQuery("#marker-chromosome").val(SEL_CHROM);
523             SEL_CHROM = undefined;
524             updatePositions();
525         }
526     }
531  * Update the start/end postions based on the selected ref/map and chrom
532  * - update position units
533  * - enable fields only when ref/map and chrom have been selected
534  */
535 function updatePositions() {
536     let refmap = jQuery("#marker-refmap").val();
537     let chrom = jQuery("#marker-chromosome").val();
538     let units = jQuery("#marker-refmap option:selected").data("units");
540     let pos_disabled = refmap === '' || chrom === null || ['', 'Loading...'].includes(chrom);
541     jQuery(".marker-position-units").html(units ? units : '&nbsp;&nbsp;');
542     jQuery("#marker-position-start").attr("disabled", pos_disabled);
543     jQuery("#marker-position-end").attr("disabled", pos_disabled);
544     if ( pos_disabled ) { 
545         jQuery("#marker-position-start").val("");
546         jQuery("#marker-position-end").val("");
547     }
552  * Update the list of species
553  */
554 function updateSpecies() {
555     let refmap_species = jQuery("#marker-refmap option:selected").data("species");
556     let protocol_species = jQuery("#marker-protocol option:selected").data("species");
557     let species = jQuery("#marker-species").val();
559     if ( SPECIES ) {
560         let ad = refmap_species 
561             || (protocol_species && !Array.isArray(protocol_species)) 
562             || (protocol_species && Array.isArray(protocol_species) && protocol_species.length === 1) 
563             ? 'disabled' : '';
564         let html = "<option value='' " + ad + ">Any Species</option>";
565         for ( let i = 0; i < SPECIES.length; i++ ) {
566             let d = (refmap_species && refmap_species !== SPECIES[i]) 
567                 || (protocol_species && !Array.isArray(protocol_species) && protocol_species !== SPECIES[i])
568                 || (protocol_species && Array.isArray(protocol_species) && !protocol_species.includes(SPECIES[i])) 
569                 ? 'disabled' : '';
570             let s = d !== 'disabled' && SPECIES[i] === species ? 'selected' : '';
571             html += "<option value='" + SPECIES[i] + "' " + d + " " + s + ">";
572             html += SPECIES[i];
573             html += "</option>";
574         }
576         jQuery("#marker-species").html(html);
577         jQuery("#marker-species").attr("disabled", false);
579         // Set intial value, if not already set
580         if ( SEL_SPECIES ) {
581             jQuery("#marker-species").val(SEL_SPECIES);
582             SEL_SPECIES = undefined;
583         }
584     }
589  * Update the list of protocols
590  */
591 function updateProtocols() {
592     let protocol = jQuery("#marker-protocol").val();
593     let refmap = jQuery("#marker-refmap").val();
594     let type = jQuery("#marker-refmap option:selected").data("type");
595     let species = jQuery("#marker-species").val();
597     if ( PROTOCOLS ) {
598         let html = "";
599         html += "<option value=''>Any Protocol</option>";        
600         
601         html += "<optgroup label='Protocols of Genotyped Markers'>";
602         for ( let i = 0; i < PROTOCOLS.genotyped.length; i++ ) {
603             let pi = PROTOCOLS.genotyped[i].nd_protocol_id;
604             let pn = PROTOCOLS.genotyped[i].nd_protocol_name;
605             let sn = PROTOCOLS.genotyped[i].species_name;
606             let rn = PROTOCOLS.genotyped[i].reference_genome_name;
608             let d = (type && type !== 'genotyped') || (refmap && refmap !== rn) || (species && sn !== species) ? 'disabled' : ''
609             let s = d !== 'disabled' && protocol && pn === protocol ? 'selected' : '';
610             html += "<option value='" + pi + "' data-type='genotyped' data-species='" + sn + "' data-refmap='" + rn + "' " + d + " " + s + ">";
611             html += pn + " (" + sn + " / " + rn + ")";
612             html += "</option>";
613         }
615         html += "<optgroup label='Protocols of Mapped Markers'>";
616         for ( let i = 0; i < PROTOCOLS.mapped.length; i++ ) {
617             let pn = PROTOCOLS.mapped[i].protocol;
618             let ss = PROTOCOLS.mapped[i].species;
619             let ss_str = JSON.stringify(ss);
620             let ms = PROTOCOLS.mapped[i].maps;
621             let mns = [];
622             let mis = [];
623             for ( let j = 0; j < ms.length; j++ ) {
624                 mns.push(ms[j].map_name);
625                 mis.push(ms[j].map_id+'');
626             }
627             let mis_str = JSON.stringify(mis);
629             let d = (type && type !== 'mapped') || (refmap && !mis.includes(refmap)) || (species && !ss.includes(species)) ? 'disabled' : '';
630             let s = d !== 'disabled' && protocol && protocol === pn ? 'selected' : '';
631             html += "<option value='" + pn + "' data-type='mapped' data-species='" + ss_str + "' data-refmap='" + mis_str + "' " + d + " " + s + ">";
632             html += pn + " (" + ss.join('; ') + " / " + mns.join('; ') + ")";
633             html += "</option>";
634         }
636         jQuery("#marker-protocol").html(html);
637         jQuery("#marker-protocol").attr("disabled", false);
639         // Set intial value, if not already set
640         if ( SEL_PROTOCOL ) {
641             jQuery("#marker-protocol").val(SEL_PROTOCOL);
642             SEL_PROTOCOL = undefined;
643         }
644     }
650 // SEARCH FUNCTIONS
655  * Perform the marker search with the user input
656  *  - Build results URL
657  *  - Go to the marker search results page
658  */
659 function search() {
661     // Get user inputs
662     let marker_name = jQuery('#marker-name').val();
663     let marker_name_match = jQuery('#marker-name-match').val();
664     let refmap = jQuery("#marker-refmap").val();
665     let refmap_type = jQuery("#marker-refmap option:selected").data("type");
666     let refmap_species = jQuery("#marker-refmap option:selected").data("species");
667     let chrom = jQuery("#marker-chromosome").val();
668     let start = jQuery("#marker-position-start").val();
669     let end = jQuery("#marker-position-end").val();
670     let species = jQuery("#marker-species").val();
671     let protocol = jQuery("#marker-protocol").val();
672     let protocol_type = jQuery("#marker-protocol option:selected").data("type");
674     // Check for required params
675     if ( (start && !end) || (end && !start) ) {
676         alert("Both the start and end position are required when filtering by position");
677         return;
678     }
680     // Set search params
681     let params = {};
682     if ( marker_name ) params.marker_name = marker_name;
683     if ( marker_name_match ) params.marker_name_match = marker_name_match;
684     if ( refmap_type || protocol_type ) params.type = refmap_type || protocol_type;
685     if ( refmap ) params.refmap = refmap;
686     if ( refmap_species || species ) params.species = refmap_species || species;
687     if ( chrom && chrom !== 'Loading...' ) params.chrom = chrom;
688     if ( start ) params.start = start;
689     if ( end ) params.end = end;
690     if ( protocol ) params.protocol = protocol;
692     // Go to results page to perform search
693     let q = new URLSearchParams(params);
694     let url = "/search/variants/results?" + q.toString();
695     window.location = url;
698 </script>
703 <style>
704     h2.marker-search-header {
705         color: #000;
706         font-size: 14px;
707         border-bottom: 1px solid #ccc;
708         margin-bottom: 25px;
709     }
710     button.marker-search-button {
711         max-width: 400px;
712         margin: 25px auto;
713     }
714     label {
715         text-align: right;
716         margin-top: 7px;
717     }
718 </style>