1e6071743e73bccbc7d56a9e38e93cc1daabccfd
chmalee
  Tue Aug 22 11:59:42 2023 -0700
Make the INFO fields collapsible table have unique ids when there is more than one on an hgc page, refs #31987

diff --git src/hg/hgc/vcfClick.c src/hg/hgc/vcfClick.c
index 7176d09..7614e1c 100644
--- src/hg/hgc/vcfClick.c
+++ src/hg/hgc/vcfClick.c
@@ -146,38 +146,40 @@
         safencpy(copy, sizeof(copy), val, len);
         char *words[PATH_LEN];
         chopByChar(copy, '|', words, ArraySize(words));
         int k;
         // printTabularHeaderRow strips off (but still prints!) a trailing '|'
         // because of the regex, so enforce that here too so the rows after
         // the header don't get all out of whack
         for (k = 0;  k < headerCount;  k++)
             printf("<TD class='withThinBorder'>%s</TD>", words[k]);
         }
     puts("</TR>");
     }
 }
 
 
-static void vcfInfoDetails(struct vcfRecord *rec, char *trackName)
+static void vcfInfoDetails(struct vcfRecord *rec, char *trackName, int recordCount)
 /* Expand info keys to descriptions, then print out keys and values. */
 {
 if (rec->infoCount == 0)
     return;
 struct vcfFile *vcff = rec->file;
 puts("<table>"); // wrapper table for collapsible section
-jsBeginCollapsibleSectionFontSize(cart, trackName, "infoFields", "INFO column annotations:", FALSE, "medium");
+char infoId[32];
+safef(infoId, sizeof(infoId), "infoFields%d", recordCount);
+jsBeginCollapsibleSectionFontSize(cart, trackName, infoId, "INFO column annotations:", FALSE, "medium");
 puts("<TABLE class=\"stdTbl\">\n");
 int i;
 for (i = 0;  i < rec->infoCount;  i++)
     {
     struct vcfInfoElement *el = &(rec->infoElements[i]);
     const struct vcfInfoDef *def = vcfInfoDefForKey(vcff, el->key);
     printf("<TR valign='top'><TD align=\"right\"><B>%s:</B></TD><TD>",
            el->key);
     int j;
     enum vcfInfoType type = def ? def->type : vcfInfoString;
     if (type == vcfInfoFlag && el->count == 0)
 	printf("Yes"); // no values, so we can't call vcfPrintDatum...
     // However, if this is older VCF, type vcfInfoFlag might have a value.
     if (looksTabular(def, el))
         {
@@ -439,31 +441,31 @@
 struct dyString *dy = dyStringNew(128);
 int i;
 for (i = 0;  i < rec->alleleCount; i++)
     {
     dyStringClear(dy);
     if (showLeftBase)
 	dyStringPrintf(dy, "(%c)", leftBase);
     abbreviateLongSeq(rec->alleles[i], endLength, showLength, dy);
     if (encodeHtml)
 	displayAls[i] = htmlEncode(dy->string);
     else
 	displayAls[i] = cloneString(dy->string);
     }
 }
 
-static void vcfRecordDetails(struct trackDb *tdb, struct vcfRecord *rec)
+static void vcfRecordDetails(struct trackDb *tdb, struct vcfRecord *rec, int recordCount)
 /* Display the contents of a single line of VCF, assumed to be from seqName
  * (using seqName instead of rec->chrom because rec->chrom might lack "chr"). */
 {
 if (isNotEmpty(rec->name) && differentString(rec->name, "."))
     printf("<B>Name:</B> %s<BR>\n", rec->name);
 // Add some special URL substitution variables for ExAC/GnomAD-style links
 struct slPair *substFields = slPairNew("ref", rec->alleles[0]);
 substFields->next = slPairNew("firstAlt", rec->alleles[1]);
 char posString[64];
 safef(posString, sizeof posString, "%d", rec->chromStart+1);
 substFields->next->next = slPairNew("pos", posString);
 char *label = rec->name;
 if ((isEmpty(rec->name) || sameString(rec->name, ".")) &&
     (startsWith("exac", tdb->track) || startsWith("gnomad", tdb->track)))
     {
@@ -501,31 +503,31 @@
                               seqName, rec->chromStart, formName);
         printf("</TABLE></FORM>\n");
         }
     }
 char leftBase = rec->alleles[0][0];
 unsigned int vcfStart = vcfRecordTrimIndelLeftBase(rec);
 boolean showLeftBase = (rec->chromStart == vcfStart+1);
 (void)vcfRecordTrimAllelesRight(rec);
 char *displayAls[rec->alleleCount];
 makeDisplayAlleles(rec, showLeftBase, leftBase, 20, TRUE, FALSE, displayAls);
 printPosOnChrom(seqName, rec->chromStart, rec->chromEnd, NULL, FALSE, rec->name);
 printf("<B>Reference allele:</B> %s<BR>\n", displayAls[0]);
 vcfAltAlleleDetails(rec, displayAls);
 vcfQualDetails(rec);
 vcfFilterDetails(rec);
-vcfInfoDetails(rec, tdb->track);
+vcfInfoDetails(rec, tdb->track, recordCount);
 pgSnpCodingDetail(rec);
 makeDisplayAlleles(rec, showLeftBase, leftBase, 5, FALSE, TRUE, displayAls);
 vcfGenotypesDetails(rec, tdb, displayAls);
 }
 
 void doVcfDetailsCore(struct trackDb *tdb, char *fileOrUrl, boolean isTabix)
 /* Show item details using fileOrUrl. */
 {
 genericHeader(tdb, NULL);
 int start = cartInt(cart, "o");
 int end = cartInt(cart, "t");
 int vcfMaxErr = -1;
 struct vcfFile *vcff = NULL;
 /* protect against temporary network or parsing error */
 struct errCatch *errCatch = errCatchNew();
@@ -537,33 +539,38 @@
 	vcff = vcfTabixFileAndIndexMayOpen(fileOrUrl, indexUrl, seqName, start, end, vcfMaxErr, -1);
         }
     else
 	vcff = vcfFileMayOpen(fileOrUrl, seqName, start, end, vcfMaxErr, -1, TRUE);
     }
 errCatchEnd(errCatch);
 if (errCatch->gotError)
     {
     if (isNotEmpty(errCatch->message->string))
 	warn("%s", errCatch->message->string);
     }
 errCatchFree(&errCatch);
 if (vcff != NULL)
     {
     struct vcfRecord *rec;
+    // use the recordCount to make up unique id strings for html collapsible INFO section later
+    int recordCount = 0;
     for (rec = vcff->records;  rec != NULL;  rec = rec->next)
+        {
         if (rec->chromStart == start && rec->chromEnd == end) // in pgSnp mode, don't get name
-	    vcfRecordDetails(tdb, rec);
+            vcfRecordDetails(tdb, rec, recordCount);
+        recordCount++;
+        }
     }
 else
     printf("Sorry, unable to open %s<BR>\n", fileOrUrl);
 printTrackHtml(tdb);
 }
 
 
 
 void doVcfTabixDetails(struct trackDb *tdb, char *item)
 /* Show details of an alignment from a VCF file compressed and indexed by tabix. */
 {
 knetUdcInstall();
 if (udcCacheTimeout() < 300)
     udcSetCacheTimeout(300);
 struct sqlConnection *conn = NULL;