689349fca5a4865a1891db8cd39d392657b2b09b
jcasper
  Wed Apr 22 02:54:09 2026 -0700
Replacing the subtrackUrl setting for faceted composites with subtrackUrls,
which supports outlinks in multiple fields.  refs #36320

diff --git src/hg/hgTrackUi/hgTrackUi.c src/hg/hgTrackUi/hgTrackUi.c
index d4d3ac60db7..5839d4bd2a6 100644
--- src/hg/hgTrackUi/hgTrackUi.c
+++ src/hg/hgTrackUi/hgTrackUi.c
@@ -3048,31 +3048,31 @@
     "<style>body.cgi { background: #F0F0F0; }"
     "table.hgInside { background: #FFFFFF; }</style>";
 const char placeholderDiv[] = "<div id='metadata-placeholder'></div>\n";
 
 // --- Get data from 'settings' field in 'trackDb' entry ---
 // required
 const char *metaDataUrl = trackDbSetting(tdb, "metaDataUrl");
 const char *primaryKey = trackDbSetting(tdb, "primaryKey");
 
 struct slPair *dataTypes = parseDataTypes(tdb);
 boolean hasDataTypes = (dataTypes != NULL);
 
 // optional
 const char *colorSettingsUrl = (const char *)hashFindVal(tdb->settingsHash, "colorSettingsUrl");
 const char *maxCheckboxes = (const char *)hashFindVal(tdb->settingsHash, "maxCheckboxes");
-const char *subtrackUrl = trackDbSetting(tdb, "subtrackUrl");
+const char *subtrackUrls = trackDbSetting(tdb, "subtrackUrls");
 // --- done parsing values from trackDb.settings ---
 
 const char *metaDataId = tdb->track;
 const int metaDataIdLen = strlen(metaDataId);
 
 printf(pageStyle);       // css
 printf(placeholderDiv);  // placholder
 
 // start by figuring out what's on by default and hasn't been overridden
 struct hash *defaultOn = hashNew(0);
 for (struct trackDb *st = tdb->subtracks; st != NULL; st = st->next)
     {
     char *setting = NULL;
     char *words[2];
     boolean enabled = TRUE;
@@ -3223,32 +3223,46 @@
     hashElFreeList(&elList);
     }
 jsonWriteListEnd(jw);
 
 jsonWriteString(jw, "mdid", (char *)metaDataId);
 jsonWriteString(jw, "primaryKey", (char *)primaryKey);  // must exist
 if (maxCheckboxes) // only if present in trackDb.settings entry
     jsonWriteString(jw, "maxCheckboxes", (char *)maxCheckboxes);
 if (colorSettingsUrl) // only if present in trackDb.settings entry
     jsonWriteString(jw, "colorSettingsUrl", cgiEncode((char *)colorSettingsUrl));
 jsonWriteString(jw, "metadataUrl", cgiEncode((char *)metaDataUrl));
 jsonWriteString(jw, "track", tdb->track);
 char *defaultSortField = trackDbSetting(tdb, "defaultSortField");
 if (isNotEmpty(defaultSortField))
     jsonWriteString(jw, "defaultSortField", defaultSortField);
-if (subtrackUrl)
-    jsonWriteString(jw, "subtrackUrl", (char *)subtrackUrl);
+if (isNotEmpty(subtrackUrls))
+    {
+    struct slPair *pairs = slPairListFromString((char *)subtrackUrls, TRUE);
+    if (pairs)
+        {
+        jsonWriteObjectStart(jw, "subtrackUrls");
+        for (struct slPair *p = pairs; p != NULL; p = p->next)
+            {
+            char *encoded = htmlEncode((char *)p->val);
+            jsonWriteString(jw, p->name, encoded);
+            freeMem(encoded);
+            }
+        jsonWriteObjectEnd(jw);
+        }
+    slPairFreeValsAndList(&pairs);
+    }
 if (isNotEmpty(cartOptionalString(cart, "udcTimeout")))
     jsonWriteBoolean(jw, "udcTimeout", TRUE);
 
 jsonWriteObjectEnd(jw);
 printf("<script id=\"app-data\" type=\"application/json\">%s</script>\n", jw->dy->string);
 jsonWriteFree(&jw);
 /* --- END embedded JSON data --- */
 
 jsIncludeFile("dataTables-2.2.2.min.js", NULL);
 jsIncludeFile("dataTables.select-3.0.0.min.js", NULL);
 jsIncludeFile("facetedComposite.js", NULL);
 
 webIncludeResourceFile("dataTables-2.2.2.min.css");
 webIncludeResourceFile("dataTables.select-3.0.0.min.css");
 webIncludeResourceFile("facetedComposite.css");