1b9262ae53dd23ca560dcc0e90773cfdd3b2ba3c
kate
  Fri Jan 9 14:51:47 2015 -0800
Refactor subtrack table construction (500 line function)
diff --git src/hg/lib/hui.c src/hg/lib/hui.c
index 49d5293..d9a1004 100644
--- src/hg/lib/hui.c
+++ src/hg/lib/hui.c
@@ -3978,173 +3978,90 @@
 
 char *date = NULL;
 
 if (metadataForTable(db,trackDb,NULL) != NULL)
     {
     date = cloneString((char *)metadataFindValue(trackDb,"dateUnrestricted"));
     if (date != NULL)
         date = strSwapChar(date, ' ', 0);   // Truncate time (not expected, but just in case)
 
     if (excludePast && !isEmpty(date) && dateIsOld(date, MDB_ENCODE_DATE_FORMAT))
         freez(&date);
     }
 return date;
 }
 
-static void compositeUiSubtracks(char *db, struct cart *cart, struct trackDb *parentTdb)
-// Display list of subtracks and descriptions with checkboxes to control visibility and
-// possibly other nice things including links to schema and metadata and a release date.
-{
-struct trackDb *subtrack;
-struct dyString *dyHtml = newDyString(SMALLBUF);
-//char *colors[2]   = { COLOR_BG_DEFAULT,
-//                      COLOR_BG_ALTDEFAULT };
-char *colors[2]   = { "bgLevel1",
-                      "bgLevel1" };
-int colorIx = COLOR_BG_DEFAULT_IX; // Start with non-default allows alternation
-
-// Get list of leaf subtracks to work with
-struct slRef *subtrackRef, *subtrackRefList = trackDbListGetRefsToDescendantLeaves(parentTdb->subtracks);
-
-// Look for dividers, heirarchy, dimensions, sort and dragAndDrop!
-char **lastDivide = NULL;
-dividers_t *dividers = dividersSettingGet(parentTdb);
-if (dividers)
-    lastDivide = needMem(sizeof(char*)*dividers->count);
-hierarchy_t *hierarchy = hierarchySettingGet(parentTdb);
-
-membersForAll_t* membersForAll = membersForAllSubGroupsGet(parentTdb,NULL);
-sortOrder_t* sortOrder = sortOrderGet(cart,parentTdb);
-boolean preSorted = FALSE;
-boolean useDragAndDrop = sameOk("subTracks",trackDbSetting(parentTdb, "dragAndDrop"));
-char buffer[SMALLBUF];
-char *displaySubs = NULL;
-int subCount = slCount(subtrackRefList);
-#define LARGE_COMPOSITE_CUTOFF 30
-if (subCount > LARGE_COMPOSITE_CUTOFF && membersForAll->dimensions != NULL)
-    {
-    // ignore displaySubtracks setting for large composites with a matrix as
-    // matrix effectively shows all
-    safef(buffer,SMALLBUF,"%s.displaySubtracks",parentTdb->track);
-    displaySubs = cartUsualString(cart, buffer,"some"); // track specific defaults to only selected
-    }
-else
-    {
-    displaySubs = cartUsualString(cart, "displaySubtracks", "all"); // browser wide defaults to all
-    }
-boolean displayAll = sameString(displaySubs, "all");
-
-// Determine whether there is a restricted until date column
-boolean restrictions = FALSE;
-for (subtrackRef = subtrackRefList; subtrackRef != NULL; subtrackRef = subtrackRef->next)
-    {
-    subtrack = subtrackRef->val;
-    (void)metadataForTable(db,subtrack,NULL);
-    if (NULL != metadataFindValue(subtrack,"dateUnrestricted"))
-        {
-        restrictions = TRUE;
-        break;
-        }
-    }
+/* Subtrack configuration settings */
 
-// Table wraps around entire list so that "Top" link can float to the correct place.
-cgiDown(0.7);
-printf("<table><tr><td class='windowSize'>");
-printf("<A NAME='DISPLAY_SUBTRACKS'></A>");
-if (sortOrder != NULL)
+struct subtrackConfigSettings 
     {
-    // First table row contains the display "selected/visible" or "all" radio buttons
-    // NOTE: list subtrack radio buttons are inside tracklist table header if
-    //       there are no sort columns.  The reason is to ensure spacing of lines
-    //       column headers when the only column header is "Restricted Until"
-    printf("<B>List subtracks:&nbsp;");
-    char javascript[JBUFSIZE];
-    safef(javascript, sizeof(javascript),
-          "class='allOrOnly' onclick='showOrHideSelectedSubtracks(true);'");
-    if (subCount > LARGE_COMPOSITE_CUTOFF)
-        safef(buffer,SMALLBUF,"%s.displaySubtracks",parentTdb->track);
-    else
-        safecpy(buffer,SMALLBUF,"displaySubtracks");
-    cgiMakeOnClickRadioButton(buffer, "selected", !displayAll,javascript);
-    puts("only selected/visible &nbsp;&nbsp;");
-    safef(javascript, sizeof(javascript),
-          "class='allOrOnly' onclick='showOrHideSelectedSubtracks(false);'");
-    cgiMakeOnClickRadioButton(buffer, "all", displayAll,javascript);
-    printf("all</B>");
-    if (slCount(subtrackRefList) > 5)
-        printf("&nbsp;&nbsp;&nbsp;&nbsp;(<span class='subCBcount'></span>)");
-    makeTopLink(parentTdb);
-    printf("</td></tr></table>");
-    }
-else
-    makeTopLink(parentTdb);
+    sortOrder_t *sortOrder; /* from trackDb setting */
+    boolean useDragAndDrop; /* from trackDb setting */
+    boolean restrictions;   /* from metadata ? */
+    boolean colorPatch;     /* from trackDb setting */
+    boolean displayAll;     /* from radiobutton */
+    int bgColorIx;          /* from logic over other settings */
+    int columnCount;        /* determined from trackDb settings */
+    };
+#define LARGE_COMPOSITE_CUTOFF 30
 
-// Now we can start in on the table of subtracks  It may be sortable and/or dragAndDroppable
-printf("\n<TABLE CELLSPACING='2' CELLPADDING='0' border='0'");
-dyStringClear(dyHtml);
-if (sortOrder != NULL)
-    dyStringPrintf(dyHtml, "sortable");
-if (useDragAndDrop)
-    {
-    if (dyStringLen(dyHtml) > 0)
-        dyStringAppendC(dyHtml,' ');
-    dyStringPrintf(dyHtml, "tableWithDragAndDrop");
-    }
-printf(" class='subtracks");
-if (dyStringLen(dyHtml) > 0)
+static void printSubtrackTableHeader(struct trackDb *parentTdb, struct slRef *subtrackRefList,
+                                    struct subtrackConfigSettings *settings)
+/* Print header of subtrack table, including classes describing display appearance and behavior.
+   Return number of columns */
 {
-    printf(" bglevel1 %s'",dyStringContents(dyHtml));
-    colorIx = COLOR_BG_ALTDEFAULT_IX;
-    }
+char buffer[SMALLBUF];
+boolean useDragAndDrop = settings->useDragAndDrop;
+sortOrder_t *sortOrder = settings->sortOrder;
 if (sortOrder != NULL)
-    puts("'><THEAD class=sortable>");
+    puts("<THEAD class=sortable>");
 else
-    puts("'><THEAD>");
-
-boolean doColorPatch = trackDbSettingOn(parentTdb, "showSubtrackColorOnUi");
+    puts("<THEAD>");
 int colspan = 3;
 if (sortOrder != NULL)
     colspan = sortOrder->count+2;
 else if (!tdbIsMultiTrack(parentTdb)) // An extra column for subVis/wrench so dragAndDrop works
     colspan++;
-if (doColorPatch)
+if (settings->colorPatch)
     colspan += 1;
 int columnCount = 0;
 if (sortOrder != NULL)
-    printf("<TR id=\"subtracksHeader\" class='sortable%s'>\n",useDragAndDrop?" nodrop nodrag":"");
+    printf("<TR id=\"subtracksHeader\" class='sortable%s'>\n",
+                useDragAndDrop ? " nodrop nodrag" : "");
 else
     {
     printf("<TR%s>", useDragAndDrop ? " id='noDrag' class='nodrop nodrag'" : "");
     // First table row contains the display "selected/visible" or "all" radio buttons
     // NOTE: list subtrack radio buttons are inside tracklist table header if
     //       there are no sort columns.  The reason is to ensure spacing of lines
     //       column headers when the only column header is "Restricted Until"
     printf("<TD colspan='%d'><B>List subtracks:&nbsp;", colspan);
     char javascript[JBUFSIZE];
     safef(javascript, sizeof(javascript),
           "class='allOrOnly' onclick='showOrHideSelectedSubtracks(true);'");
+    int subCount = slCount(subtrackRefList);
     if (subCount > LARGE_COMPOSITE_CUTOFF)
         safef(buffer,SMALLBUF,"%s.displaySubtracks",parentTdb->track);
     else
         safecpy(buffer,SMALLBUF,"displaySubtracks");
-    cgiMakeOnClickRadioButton(buffer, "selected", !displayAll,javascript);
+    cgiMakeOnClickRadioButton(buffer, "selected", !settings->displayAll,javascript);
     puts("only selected/visible &nbsp;&nbsp;");
     safef(javascript, sizeof(javascript),
           "class='allOrOnly' onclick='showOrHideSelectedSubtracks(false);'");
-    cgiMakeOnClickRadioButton(buffer, "all", displayAll,javascript);
+    cgiMakeOnClickRadioButton(buffer, "all", settings->displayAll,javascript);
     printf("all</B>");
-    if (slCount(subtrackRefList) > 5)
+    if (subCount > 5)
         printf("&nbsp;&nbsp;&nbsp;&nbsp;(<span class='subCBcount'></span>)");
     puts("</TD>");
     columnCount = colspan;
     }
 
 // Add column headers which are sort button links
 if (sortOrder != NULL)
     {
     printf("<TH>&nbsp;<INPUT TYPE=HIDDEN NAME='%s' class='sortOrder' VALUE='%s'></TH>\n",
            sortOrder->htmlId, sortOrder->sortOrder); // keeing track of sortOrder
     columnCount++;
     if (!tdbIsMultiTrack(parentTdb))  // An extra column for subVis/wrench so dragAndDrop works
         {
         printf("<TH></TH>\n");
         columnCount++;
@@ -4170,73 +4087,125 @@
         }
 
     // longLabel column
     assert(sameString(SORT_ON_TRACK_NAME,sortOrder->column[sIx]));
     printf("<TH id='%s' class='sortable%s sort%d' onclick='tableSortAtButtonPress(this);' "
            "align='left'>&nbsp;&nbsp;Track Name",
            sortOrder->column[sIx],(sortOrder->forward[sIx]?"":" sortRev"),sortOrder->order[sIx]);
     printf("<sup>%s%d</sup>",(sortOrder->forward[sIx]?"&darr;":"&uarr;"),sortOrder->order[sIx]);
     puts("</TH>");
     columnCount++;
     }
 puts("<TH>&nbsp;</TH>"); // schema column
 columnCount++;
 
 // Finally there may be a restricted until column
-if (restrictions)
+if (settings->restrictions)
     {
     if (sortOrder != NULL)
         {
         int sIx=sortOrder->count-1;
         assert(sameString(SORT_ON_RESTRICTED,sortOrder->column[sIx]));
         printf("<TH id='%s' class='sortable%s sort%d' onclick='tableSortAtButtonPress(this);' "
                 "align='left'>&nbsp;Restricted Until", sortOrder->column[sIx],
                 (sortOrder->forward[sIx]?"":" sortRev"),sortOrder->order[sIx]);
         printf("<sup>%s%d</sup>",(sortOrder->forward[sIx] ? "&darr;" : "&uarr;"),
                sortOrder->order[sIx]);
         puts("</TH>");
         }
     else
         {
         printf("<TH align='center'>&nbsp;");
         printf("<A HREF=\'%s\' TARGET=BLANK>Restricted Until</A>", ENCODE_DATA_RELEASE_POLICY);
         puts("&nbsp;</TH>");
         }
     columnCount++;
     }
 puts("</TR></THEAD>"); // The end of the header section.
+settings->columnCount = columnCount;
+}
+
+static void printSubtrackTableFooter(int subCount, struct subtrackConfigSettings *settings)
+/* Print footer with restriction policy if needed */
+{
+boolean restrictions = settings->restrictions;
+sortOrder_t *sortOrder = settings->sortOrder;
+int columnCount = settings->columnCount;
+
+if (subCount > 5 || (restrictions && sortOrder != NULL))
+    {
+    printf("<TFOOT style='background-color:%s;'><TR valign='top'>", COLOR_BG_DEFAULT_DARKER);
+    if (restrictions && sortOrder != NULL)
+        printf("<TD colspan=%d>&nbsp;&nbsp;&nbsp;&nbsp;",columnCount-1);
+    else
+        printf("<TD colspan=%d>&nbsp;&nbsp;&nbsp;&nbsp;",columnCount);
+
+    // Count of subtracks is filled in by javascript.
+    if (subCount > 5)
+        printf("<span class='subCBcount'></span>\n");
+
+    // Restriction policy needs a link
+    if (restrictions && sortOrder != NULL)
+        printf("</TD><TH><A HREF='%s' TARGET=BLANK style='font-size:.9em;'>Restriction Policy</A>",
+               ENCODE_DATA_RELEASE_POLICY);
+
+    printf("</TD></TR></TFOOT>\n");
+    }
+}
+
+static void printSubtrackTableBody(struct trackDb *parentTdb, struct slRef *subtrackRefList,
+                                    struct subtrackConfigSettings *settings, struct cart *cart)
+/* Print list of subtracks */
+{
+sortOrder_t *sortOrder = settings->sortOrder;
+boolean useDragAndDrop = settings->useDragAndDrop;
+boolean restrictions = settings->restrictions;
+struct dyString *dyHtml = newDyString(SMALLBUF);
+char buffer[SMALLBUF];
+char *db = cartString(cart, "db");
 
 // The subtracks need to be sorted by priority but only sortable and dragable will have
 // non-default (cart) priorities to sort on
+boolean preSorted = FALSE;
 if (sortOrder != NULL || useDragAndDrop)
     {
     // preserves user's prev sort/drags
     preSorted = tdbRefSortPrioritiesFromCart(cart, &subtrackRefList);
     printf("<TBODY class='%saltColors'>\n", (sortOrder != NULL ? "sortable " : "") );
     }
 else
     {
     slSort(&subtrackRefList, trackDbRefCmp);  // straight from trackDb.ra
     preSorted = TRUE;
     puts("<TBODY>");
     }
 
 // Finally the big "for loop" to list each subtrack as a table row.
 printf("\n<!-- ----- subtracks list ----- -->\n");
+membersForAll_t* membersForAll = membersForAllSubGroupsGet(parentTdb,NULL);
+struct slRef *subtrackRef;
+
+/* Color handling ?? */
+//char *colors[2]   = { COLOR_BG_DEFAULT,
+//                      COLOR_BG_ALTDEFAULT };
+char *colors[2]   = { "bgLevel1",
+                      "bgLevel1" };
+int colorIx = settings->bgColorIx;
+
 for (subtrackRef = subtrackRefList; subtrackRef != NULL; subtrackRef = subtrackRef->next)
     {
-    subtrack = subtrackRef->val;
+    struct trackDb *subtrack = subtrackRef->val;
     int ix;
 
     // Determine whether subtrack is checked, visible, configurable, has group membership, etc.
     int fourState = subtrackFourStateChecked(subtrack,cart);
     boolean checkedCB = fourStateChecked(fourState);
     boolean enabledCB = fourStateEnabled(fourState);
     boolean visibleCB = fourStateVisible(fourState);
     membership_t *membership = subgroupMembershipGet(subtrack);
     eCfgType cType = cfgNone;
     if (!tdbIsMultiTrack(parentTdb))  // MultiTracks never have configurable subtracks!
         cType = cfgTypeFromTdb(subtrack,FALSE);
     if (cType != cfgNone)
         {
         // Turn off configuring for certain track type or if explicitly turned off
         int cfgSubtrack = configurableByAjax(subtrack,cType);
@@ -4253,40 +4222,46 @@
                                                membersForAll->members[dimV]->tags,
                                                membersForAll->members[dimV]->count)))
                     {
                     if (membersForAll->members[dimV]->subtrackCount[ix2] < 2)
                         cType = cfgNone;
                     }
                 }
             }
         else if (slCount(subtrackRefList) < 2   // don't bother if there is a single subtrack
              && cfgTypeFromTdb(parentTdb,FALSE) != cfgNone) // but the composite is configurable.
             cType = cfgNone;
         }
 
     if (sortOrder == NULL && !useDragAndDrop)
         {
+        char **lastDivide = NULL;
+        dividers_t *dividers = dividersSettingGet(parentTdb);
+        if (dividers)
+            lastDivide = needMem(sizeof(char*)*dividers->count);
         if (divisionIfNeeded(lastDivide,dividers,membership) )
             colorIx = (colorIx == COLOR_BG_DEFAULT_IX ? COLOR_BG_ALTDEFAULT_IX
                                                       : COLOR_BG_DEFAULT_IX);
+        dividersFree(&dividers);
         }
 
     // Start the TR which must have an id that is directly related to the checkBox id
     char *id = checkBoxIdMakeForTrack(subtrack,membersForAll->members,membersForAll->dimMax,
                                       membership); // view is known tag
-    printf("<TR valign='top' class='%s%s'",colors[colorIx],(useDragAndDrop?" trDraggable":""));
-    printf(" id=tr_%s%s>\n",id,(!visibleCB && !displayAll?" style='display:none'":""));
+    printf("<TR valign='top' class='%s%s'",
+                colors[colorIx],(useDragAndDrop?" trDraggable":""));
+    printf(" id=tr_%s%s>\n",id,(!visibleCB && !settings->displayAll?" style='display:none'":""));
 
     // Now the TD that holds the checkbox
     printf("<TD%s%s>",
            (enabledCB?"":" title='view is hidden'"),
            (useDragAndDrop?" class='dragHandle' title='Drag to reorder'":""));
 
     // A hidden field to keep track of subtrack order if it could change
     if (sortOrder != NULL || useDragAndDrop)
         {
         safef(buffer, sizeof(buffer), "%s.priority", subtrack->track);
         float priority = (float)cartUsualDouble(cart, buffer, subtrack->priority);
         printf("<INPUT TYPE=HIDDEN NAME='%s' class='trPos' VALUE=\"%.0f\">",
                buffer, priority); // keeing track of priority
         }
 
@@ -4319,30 +4294,31 @@
                                 (ix = stringArrayIx(membersForAll->members[dimV]->groupTag,
                                                     membership->subgroups, membership->count)))
         dyStringPrintf(dyHtml, " %s",membership->membership[ix]);  // Saved view for last
 
     // And finally the checkBox is made!
     safef(buffer, sizeof(buffer), "%s_sel", subtrack->track);
     if (!enabledCB)
         {
         dyStringAppend(dyHtml, " disabled");
         cgiMakeCheckBoxFourWay(buffer,checkedCB,enabledCB,id,dyStringContents(dyHtml),
                 "onclick='matSubCbClick(this);' style='cursor:pointer' title='view is hidden'");
         }
     else
         cgiMakeCheckBoxFourWay(buffer,checkedCB,enabledCB,id,dyStringContents(dyHtml),
                                "onclick='matSubCbClick(this);' style='cursor:pointer'");
+
     if (useDragAndDrop)
         printf("&nbsp;");
 
     if (!tdbIsMultiTrack(parentTdb))  // MultiTracks never have independent vis
         {
         printf("</TD><TD>"); // An extra column for subVis/wrench so dragAndDrop works
         enum trackVisibility vis = tdbVisLimitedByAncestors(cart,subtrack,FALSE,FALSE);
         char *view = NULL;
         if (membersForAll->members[dimV]
         && -1 != (ix = stringArrayIx(membersForAll->members[dimV]->groupTag, membership->subgroups,
                                      membership->count)))
             view = membership->membership[ix];
         char classList[256];
         if (view != NULL)
             safef(classList,sizeof(classList),"clickable fauxInput%s subVisDD %s",
@@ -4353,31 +4329,31 @@
         #define SUBTRACK_CFG_VIS "<div id= '%s_faux' class='%s' style='width:65px;' " \
                                  "onclick='return subCfg.replaceWithVis(this,\"%s\",true);'>" \
                                  "%s</div>\n"
         printf(SUBTRACK_CFG_VIS,subtrack->track,classList,subtrack->track,hStringFromTv(vis));
         if (cType != cfgNone)  // make a wrench
             {
             #define SUBTRACK_CFG_WRENCH "<span class='clickable%s' onclick='return " \
                                         "subCfg.cfgToggle(this,\"%s\");' title='Configure this " \
                                         "subtrack'><img src='../images/wrench.png'></span>\n"
             printf(SUBTRACK_CFG_WRENCH,(visibleCB ? "":" disabled"),subtrack->track);
             }
         }
     printf("</TD>");
 
     // A color patch which helps distinguish subtracks in some types of composites
-    if (doColorPatch)
+    if (settings->colorPatch)
         {
         printf("<TD BGCOLOR='#%02X%02X%02X'>&nbsp;&nbsp;&nbsp;&nbsp;</TD>",
                subtrack->colorR, subtrack->colorG, subtrack->colorB);
         }
 
     // If sortable, then there must be a column per sortable dimension
     if (sortOrder != NULL)
         {
         int sIx=0;
         for (sIx=0;sIx<sortOrder->count;sIx++)
             {
             ix = stringArrayIx(sortOrder->column[sIx], membership->subgroups, membership->count);
                                 // TODO: Sort needs to expand from subGroups to labels as well
             if (ix >= 0)
                 {
@@ -4387,31 +4363,33 @@
                 else
                     titleRoot = labelRoot(membership->titles[ix],NULL);
                 // Each sortable column requires hidden goop (in the "abbr" field currently)
                 // which is the actual sort on value
                 printf("<TD id='%s_%s' abbr='%s' align='left'>&nbsp;",
                        subtrack->track,sortOrder->column[sIx],membership->membership[ix]);
                 printf("%s",titleRoot);
                 puts("</TD>");
                 freeMem(titleRoot);
                 }
             }
         }
     else  // Non-sortable tables do not have sort by columns but will display a short label
         { // (which may be a configurable link)
         printf("<TD>&nbsp;");
+        hierarchy_t *hierarchy = hierarchySettingGet(parentTdb);
         indentIfNeeded(hierarchy,membership);
+        hierarchyFree(&hierarchy);
         printf("%s",subtrack->shortLabel);
         puts("</TD>");
         }
 
     // The long label column (note that it may have a metadata dropdown)
     printf("<TD title='select to copy'>&nbsp;%s", subtrack->longLabel);
     if (trackDbSetting(parentTdb, "wgEncode") && trackDbSetting(subtrack, "accession"))
         printf(" [GEO:%s]", trackDbSetting(subtrack, "accession"));
     compositeMetadataToggle(db,subtrack,NULL,TRUE,FALSE);
     printf("&nbsp;");
 
     // Embedded cfg dialogs are within the TD that contains the longLabel.
     //  This allows a wide item to be embedded in the table
     if (cType != cfgNone)
         {
@@ -4443,59 +4421,156 @@
             if (dateIsOld(dateDisplay, MDB_ENCODE_DATE_FORMAT))
                 printf("</TD>\n<TD align='center' nowrap style='color: #BBBBBB;'>&nbsp;%s&nbsp;",
                        dateDisplay);
             else
                 printf("</TD>\n<TD align='center'>&nbsp;%s&nbsp;", dateDisplay);
             }
         }
 
     // End of row and free ourselves of this subtrack
     puts("</TD></TR>\n");
     checkBoxIdFree(&id);
     }
 
 // End of the table
 puts("</TBODY>");
-if (slCount(subtrackRefList) > 5 || (restrictions && sortOrder != NULL))
+dyStringFree(&dyHtml)
+membersForAllSubGroupsFree(parentTdb,&membersForAll);
+}
+
+static void printSubtrackTable(struct trackDb *parentTdb, struct slRef *subtrackRefList,
+                                struct subtrackConfigSettings *settings, struct cart *cart)
+/* Print table of subtracks */
 {
-    printf("<TFOOT style='background-color:%s;'><TR valign='top'>", COLOR_BG_DEFAULT_DARKER);
-    if (restrictions && sortOrder != NULL)
-        printf("<TD colspan=%d>&nbsp;&nbsp;&nbsp;&nbsp;",columnCount-1);
+// Print table tag
+printf("\n<TABLE CELLSPACING='2' CELLPADDING='0' border='0'");
+struct dyString *dyHtml = newDyString(SMALLBUF);
+if (settings->sortOrder != NULL)
+    dyStringPrintf(dyHtml, "sortable");
+if (settings->useDragAndDrop)
+    {
+    if (dyStringLen(dyHtml) > 0)
+        dyStringAppendC(dyHtml,' ');
+    dyStringPrintf(dyHtml, "tableWithDragAndDrop");
+    }
+printf(" class='subtracks");
+if (dyStringLen(dyHtml) > 0)
+    {
+    printf(" bglevel1 %s'",dyStringContents(dyHtml));
+    settings->bgColorIx = COLOR_BG_ALTDEFAULT_IX;
+    }
 else
-        printf("<TD colspan=%d>&nbsp;&nbsp;&nbsp;&nbsp;",columnCount);
+    settings->bgColorIx = COLOR_BG_DEFAULT_IX; // Start with non-default allows alternation
+puts("'>");
+dyStringFree(&dyHtml)
 
-    // Count of subtracks is filled in by javascript.
+// save count of subtracks for use by footer code
+int subCount = slCount(subtrackRefList);
+
+printSubtrackTableHeader(parentTdb, subtrackRefList, settings);
+printSubtrackTableBody(parentTdb, subtrackRefList, settings, cart);
+printSubtrackTableFooter(subCount, settings);
+puts("</TABLE>");
+}
+
+static void compositeUiSubtracks(char *db, struct cart *cart, struct trackDb *parentTdb)
+// Display list of subtracks and descriptions with checkboxes to control visibility and
+// possibly other nice things including links to schema and metadata and a release date.
+{
+char buffer[SMALLBUF];
+struct trackDb *subtrack;
+
+// Get list of leaf subtracks to work with
+struct slRef *subtrackRef, *subtrackRefList = trackDbListGetRefsToDescendantLeaves(parentTdb->subtracks);
+
+membersForAll_t* membersForAll = membersForAllSubGroupsGet(parentTdb,NULL);
+sortOrder_t* sortOrder = sortOrderGet(cart,parentTdb);
+char *displaySubs = NULL;
+int subCount = slCount(subtrackRefList);
+if (subCount > LARGE_COMPOSITE_CUTOFF && membersForAll->dimensions != NULL)
+    {
+    // ignore displaySubtracks setting for large composites with a matrix as
+    // matrix effectively shows all
+    safef(buffer,SMALLBUF,"%s.displaySubtracks",parentTdb->track);
+    displaySubs = cartUsualString(cart, buffer,"some"); // track specific defaults to only selected
+    }
+else
+    {
+    displaySubs = cartUsualString(cart, "displaySubtracks", "all"); // browser wide defaults to all
+    }
+boolean displayAll = sameString(displaySubs, "all");
+
+// Table wraps around entire list so that "Top" link can float to the correct place.
+cgiDown(0.7);
+printf("<table><tr><td class='windowSize'>");
+printf("<A NAME='DISPLAY_SUBTRACKS'></A>");
+if (sortOrder != NULL)
+    {
+    // First table row contains the display "selected/visible" or "all" radio buttons
+    // NOTE: list subtrack radio buttons are inside tracklist table header if
+    //       there are no sort columns.  The reason is to ensure spacing of lines
+    //       column headers when the only column header is "Restricted Until"
+    printf("<B>List subtracks:&nbsp;");
+    char javascript[JBUFSIZE];
+    safef(javascript, sizeof(javascript),
+          "class='allOrOnly' onclick='showOrHideSelectedSubtracks(true);'");
+    if (subCount > LARGE_COMPOSITE_CUTOFF)
+        safef(buffer,SMALLBUF,"%s.displaySubtracks",parentTdb->track);
+    else
+        safecpy(buffer,SMALLBUF,"displaySubtracks");
+    cgiMakeOnClickRadioButton(buffer, "selected", !displayAll,javascript);
+    puts("only selected/visible &nbsp;&nbsp;");
+    safef(javascript, sizeof(javascript),
+          "class='allOrOnly' onclick='showOrHideSelectedSubtracks(false);'");
+    cgiMakeOnClickRadioButton(buffer, "all", displayAll,javascript);
+    printf("all</B>");
     if (slCount(subtrackRefList) > 5)
-        printf("<span class='subCBcount'></span>\n");
+        printf("&nbsp;&nbsp;&nbsp;&nbsp;(<span class='subCBcount'></span>)");
+    makeTopLink(parentTdb);
+    printf("</td></tr></table>");
+    }
+else
+    makeTopLink(parentTdb);
 
-    // Restriction policy needs a link
-    if (restrictions && sortOrder != NULL)
-        printf("</TD><TH><A HREF='%s' TARGET=BLANK style='font-size:.9em;'>Restriction Policy</A>",
-               ENCODE_DATA_RELEASE_POLICY);
+// Get info for subtrack list
+struct subtrackConfigSettings *subtrackConfig = NULL;
+AllocVar(subtrackConfig);
 
-    printf("</TD></TR></TFOOT>\n");
+// Determine whether there is a restricted until date column
+subtrackConfig->restrictions = FALSE;
+for (subtrackRef = subtrackRefList; subtrackRef != NULL; subtrackRef = subtrackRef->next)
+    {
+    subtrack = subtrackRef->val;
+    (void)metadataForTable(db,subtrack,NULL);
+    if (NULL != metadataFindValue(subtrack,"dateUnrestricted"))
+        {
+        subtrackConfig->restrictions = TRUE;
+        break;
         }
-puts("</TABLE>");
+    }
+subtrackConfig->colorPatch = trackDbSettingOn(parentTdb, "showSubtrackColorOnUi");
+subtrackConfig->useDragAndDrop = sameOk("subTracks",trackDbSetting(parentTdb, "dragAndDrop"));
+subtrackConfig->sortOrder = sortOrder;
+subtrackConfig->displayAll = displayAll;
+
+printSubtrackTable(parentTdb, subtrackRefList, subtrackConfig, cart);
+
 if (sortOrder == NULL)
     printf("</td></tr></table>");
 
-// Finally we are free of all this
 membersForAllSubGroupsFree(parentTdb,&membersForAll);
-dyStringFree(&dyHtml)
 sortOrderFree(&sortOrder);
-dividersFree(&dividers);
-hierarchyFree(&hierarchy);
 }
 
 static void compositeUiSubtracksMatchingPrimary(char *db, struct cart *cart,
                                                 struct trackDb *parentTdb,char *primarySubtrack)
 // Display list of subtracks associated with a primary subtrack for the hgTables merge function
 {
 assert(primarySubtrack != NULL);
 char *primaryType = getPrimaryType(primarySubtrack, parentTdb);
 char htmlIdentifier[SMALLBUF];
 
 // Get list of leaf subtracks to work with and sort them
 struct slRef *subtrackRef, *subtrackRefList =
                                 trackDbListGetRefsToDescendantLeaves(parentTdb->subtracks);
 if (NULL != trackDbSetting(parentTdb, "sortOrder")
 ||  NULL != trackDbSetting(parentTdb, "dragAndDrop"))