ac48943c159a91ae0ba861823d9962e7e92efa1b
jcasper
  Wed Jan 28 01:06:25 2026 -0800
First pass at having the facetedComposite UI correctly recognize subtracks that
are on by default, refs #36320

diff --git src/hg/hgTrackUi/hgTrackUi.c src/hg/hgTrackUi/hgTrackUi.c
index a16aa2f2f22..d6dcd0f4c02 100644
--- src/hg/hgTrackUi/hgTrackUi.c
+++ src/hg/hgTrackUi/hgTrackUi.c
@@ -2947,164 +2947,207 @@
  * This function will embed sessionDb.settings/cart data in the
  * generated HTML. Instead of embedding all relevant tracks, it
  * parses the tracks named like:
  *
  *    facetedCompositeName_dataElementName_dataTypeName_sel=1
  *
  * And only sends unique dataElement and dataTypes, which might be
  * much smaller. The faceted composite assumes that selection of
  * dataTypes applies to all dataElements, but within the cart, these
  * are separate tracks, and each must be present to be drawn. But
  * the JS doesn't need the product {dataType} x {dataElement}, just
  * the union {datType} U {dataElement}. These are put in two arrays
  * in a JSON section of the HTML.
  */
 
-const int token_size = 64;
-const int query_buff_size = 256;
+const int token_size = 1024;
 
 // html elements for the controls page (from singleCellMerged)
 const char pageStyle[] =
     "<style>body.cgi { background: #F0F0F0; }"
     "table.hgInside { background: #FFFFFF; }</style>";
 const char placeholderDiv[] = "<div id='metadata-placeholder'></div>\n";
 const char openJSON[] = "<script id=\"app-data\" type=\"application/json\">{";
 const char closeJSON[] = "}</script>\n";
 const char openDataTypesJSON[] = "\"dataTypes\":{";
 const char closeDataTypesJSON[] = "}";  // closing a dict
 const char openDataElementsJSON[] = "\"dataElements\":[";
 const char closeDataElementsJSON[] = "]";  // closing an array
 const char metadataTableScriptElement[] =
     "<script type='text/javascript' src='/js/facetedComposite.js'></script>\n";
 
-// parse the hgsid as id and sessionKey
-char *hgsid = cartSessionId(cart);
-char *sessionKey = NULL;
-const int id = cartDbParseId(hgsid, &sessionKey);
-if (!sessionKey)
-    errAbort("Failed to parse session key from: %s", hgsid);
-
 // --- Get data from 'settings' field in 'trackDb' entry ---
 // required
 const char *metaDataUrl = trackDbSetting(tdb, "metaDataUrl");
 const char *primaryKey = trackDbSetting(tdb, "primaryKey");
 
 struct slName *dataTypes = parseDataTypes(tdb);
 boolean hasDataTypes = (dataTypes != NULL);
-//if (!dataTypes)
-//    errAbort("Failed to parse data types from faceted composite settings for: %s", tdb->track);
 
 // optional
 const char *colorSettingsUrl = (const char *)hashFindVal(tdb->settingsHash, "colorSettingsUrl");
 const char *maxCheckboxes = (const char *)hashFindVal(tdb->settingsHash, "maxCheckboxes");
 // --- done parsing values from trackDb.settings ---
 
 const char *metaDataId = tdb->track;
 const int metaDataIdLen = strlen(metaDataId);
 
-char queryFmt[] = "SELECT contents FROM sessionDb WHERE id='%d' AND sessionKey='%s';";
-char query[query_buff_size];
-sqlSafef(query, query_buff_size, queryFmt, id, sessionKey);
-
-struct sqlConnection *conn = hConnectCentral();
-const char *contents = sqlQuickString(conn, query);
-struct cgiParsedVars *varList = cgiParsedVarsNew((char *)contents);
-
 printf(pageStyle);       // css
 printf(placeholderDiv);  // placholder
 
+// start by figuring out what's on by default
+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;
+    if ((setting = trackDbLocalSetting(st, "parent")) != NULL)
+        {
+        char *clone = NULL;
+        if (chopLine(clone = cloneString(setting), words) >= 2)
+            if (sameString(words[1], "off"))
+                enabled = FALSE;
+        freeMem(clone);
+        }
+    if (enabled)
+        {
+        char val[1024];
+        safef(val, sizeof(val), "%s_sel", st->track);
+        hashAdd(defaultOn, val, NULL);
+        }
+    }
+
 /* --- START embedded JSON data --- */
 printf(openJSON);
 printf(openDataTypesJSON);
 // find selected data types
 int not_first = 0;
 struct slName *anySelDataType = NULL;  // non-null val will be used as flag
 if (hasDataTypes)
     {
     for (struct slName *thisType = dataTypes; thisType != NULL; thisType = thisType->next)
         {
         char toMatch[token_size];
-        safef(toMatch, token_size, "_%s_sel", thisType->name);
-        boolean dataTypeSel = FALSE;
-        for (struct cgiVar *le = varList->list; !dataTypeSel && le; le = le->next)
-            if (startsWith(metaDataId, le->name) && endsWith(le->name, toMatch))
-                dataTypeSel = TRUE;
+        safef(toMatch, token_size, "%s_*_%s_sel", metaDataId, thisType->name);
+        boolean dataTypeSel = cartVarExistsLike(cart, toMatch) || hashItemExistsLike(defaultOn, toMatch);
         printf("%s\"%s\": %d", COMMA_IF(not_first), thisType->name, dataTypeSel ? 1 : 0);
         anySelDataType = dataTypeSel ? thisType : anySelDataType;
         }
     }
 // else: dataTypes dict is empty - JS will detect this
 printf(closeDataTypesJSON);
 printf(",");  // add separator
 // find selected data sets
 printf(openDataElementsJSON);
 not_first = 0;
 if (hasDataTypes)
     {
     if (anySelDataType != NULL)
         {
-        char suffix[token_size];
-        safef(suffix, token_size, "_%s_sel", anySelDataType->name);
-        for (struct cgiVar *le = varList->list; le; le = le->next)
-            if (startsWith(metaDataId, le->name) && endsWith(le->name, suffix))
+        char toMatch[token_size];
+        safef(toMatch, token_size, "%s_*_%s_sel", metaDataId, anySelDataType->name);
+        struct slPair *mdidVars = cartVarsLike(cart, toMatch);
+        for (struct slPair *le = mdidVars; le != NULL; le = le->next)
+            {
+            if (cartBoolean(cart, le->name))
                 {
                 const char *nameStart = le->name + metaDataIdLen + 1;
                 const char *nameEnd = strchr(nameStart, '_');
                 if (nameEnd && nameEnd > nameStart)
                     {
                     const int nameLen = nameEnd - nameStart;
                     printf("%s\"%.*s\"", COMMA_IF(not_first), nameLen, nameStart);
                     }
                 }
             }
+        slPairFreeList(&mdidVars);
+        // Now add data elements on by default that haven't been modified
+        struct hashEl *el, *elList = hashElListHash(defaultOn);
+        slSort(&elList, hashElCmp);
+        for (el = elList; el != NULL; el = el->next)
+            {
+            if (!cartVarExistsLike(cart, el->name))
+                {
+                const char *nameStart = el->name + metaDataIdLen + 1;
+                const char *nameEnd = strchr(nameStart, '_');
+                if (nameEnd && nameEnd > nameStart)
+                    {
+                    const int nameLen = nameEnd - nameStart;
+                    printf("%s\"%.*s\"", COMMA_IF(not_first), nameLen, nameStart);
+                    }
+                }
+            }
+        hashElFreeList(&elList);
+        }
     }
 else
     {
     // No data types - look for {mdid}_{de}_sel pattern (no dataType component)
-    char suffix[] = "_sel";
-    for (struct cgiVar *le = varList->list; le; le = le->next)
+    char toMatch[token_size];
+    safef(toMatch, token_size, "%s_*_sel", metaDataId);
+    struct slPair *mdidVars = cartVarsLike(cart, toMatch);
+    for (struct slPair *le = mdidVars; le != NULL; le = le->next)
         {
-        if (startsWith(metaDataId, le->name) && endsWith(le->name, suffix))
+        if (cartBoolean(cart, le->name))
             {
             // Extract data element name: between mdid_ and _sel
             const char *nameStart = le->name + metaDataIdLen + 1;
             const char *nameEnd = strstr(nameStart, "_sel");
             if (nameEnd && nameEnd > nameStart)
                 {
                 const int nameLen = nameEnd - nameStart;
                 printf("%s\"%.*s\"", COMMA_IF(not_first), nameLen, nameStart);
                 }
             }
         }
+    slPairFreeList(&mdidVars);
+
+    // Now add data elements on by default that haven't been modified
+    struct hashEl *el, *elList = hashElListHash(defaultOn);
+    slSort(&elList, hashElCmp);
+    for (el = elList; el != NULL; el = el->next)
+        {
+        if (!cartVarExistsLike(cart, el->name))
+            {
+            const char *nameStart = el->name + metaDataIdLen + 1;
+            const char *nameEnd = strstr(nameStart, "_sel");
+            if (nameEnd && nameEnd > nameStart)
+                {
+                const int nameLen = nameEnd - nameStart;
+                printf("%s\"%.*s\"", COMMA_IF(not_first), nameLen, nameStart);
+                }
+            }
+        }
+    hashElFreeList(&elList);
     }
 printf(closeDataElementsJSON);
 printf(",\"mdid\": \"%s\"", metaDataId);
 printf(",\"primaryKey\": \"%s\"", primaryKey);  // must exist
 if (maxCheckboxes) // only if present in trackDb.settings entry
     printf(",\"maxCheckboxes\": \"%s\"", maxCheckboxes);
 if (colorSettingsUrl) // only if present in trackDb.settings entry
     printf(",\"colorSettingsUrl\": \"%s\"", colorSettingsUrl);
 printf(",\"metadataUrl\": \"%s\"", metaDataUrl);
 printf(closeJSON);
 /* --- END embedded JSON data --- */
 
 printf(metadataTableScriptElement);
 
 // cleanup
 slFreeList(&dataTypes);
-cgiParsedVarsFreeList(&varList);
-hDisconnectCentral(&conn);
+hashFree(&defaultOn);
 }
 
 void specificUi(struct trackDb *tdb, struct trackDb *tdbList, struct customTrack *ct, boolean ajax)
 /* Draw track specific parts of UI. */
 {
 char *track = tdb->track;
 char *db = database;
 char *liftDb = cloneString(trackDbSetting(tdb, "quickLiftDb"));
 if (liftDb != NULL) 
     db = liftDb;
 // Ideally check cfgTypeFromTdb()/cfgByCfgType() first, but with all these special cases already in
 //    place, lets be cautious at this time.
 // NOTE: Developer, please try to use cfgTypeFromTdb()/cfgByCfgType().
 
 boolean boxed = trackDbSettingClosestToHomeOn(tdb, "boxedCfg");