7e5039db1d627eceaa4459dc4b25d2ced6f55fcc
angie
  Tue Jul 28 08:55:57 2015 -0700
In groupedTrackDb, sort views alphabetically and subtracks by priority / shortLabel.

diff --git src/hg/lib/cartJson.c src/hg/lib/cartJson.c
index b1f44fb..d4f37ef 100644
--- src/hg/lib/cartJson.c
+++ src/hg/lib/cartJson.c
@@ -317,30 +317,49 @@
     {
     struct hashEl *hel;
     struct hashCookie cookie = hashFirst(tdb->settingsHash);
     while ((hel = hashNext(&cookie)) != NULL)
         {
         if (! nameIsTdbField(hel->name) && fieldOk(hel->name, fieldHash))
             {
             //#*** TODO: move jsonStringEscape inside jsonWriteString
             char *encoded = jsonStringEscape((char *)hel->val);
             jsonWriteString(jw, hel->name, encoded);
             }
         }
     }
 }
 
+static int trackDbViewCmp(const void *va, const void *vb)
+/* *** I couldn't find anything like this in hgTracks or hui, but clearly views end up
+ * *** being ordered alphabetically somehow.  Anyway...
+ * Compare two trackDbs, first by view if both have a view setting, then the usual way
+ * (by priority, then by shortLabel). */
+{
+const struct trackDb *a = *((struct trackDb **)va);
+const struct trackDb *b = *((struct trackDb **)vb);
+// The casts are necessary here unless one adds const to trackDbSetting decl
+char *viewA = trackDbSetting((struct trackDb *)a, "view");
+char *viewB = trackDbSetting((struct trackDb *)b, "view");
+int diff = 0;
+if (isNotEmpty(viewA) && isNotEmpty(viewB))
+    diff = strcmp(viewA, viewB);
+if (diff != 0)
+    return diff;
+return trackDbCmp(va, vb);
+}
+
 static struct jsonWrite *rTdbToJw(struct trackDb *tdb, struct hash *fieldHash,
                                   struct hash *excludeTypesHash, int depth, int maxDepth)
 /* Recursively build and return a new jsonWrite object with JSON for tdb and its children,
  * or NULL if tdb or all children have been filtered out by excludeTypesHash.
  * If excludeTypesHash is non-NULL, omit any tracks/views/subtracks with type in excludeTypesHash.
  * If fieldHash is non-NULL, include only the field names indexed in fieldHash. */
 {
 if (maxDepth >= 0 && depth > maxDepth)
     return NULL;
 boolean doSubtracks = (tdb->subtracks && fieldOk("subtracks", fieldHash));
 // If excludeTypesHash is given and tdb is a leaf track/subtrack, look up the first word
 // of tdb->type in excludeTypesHash; if found, return NULL.
 if (excludeTypesHash && !doSubtracks)
     {
     char typeCopy[PATH_LEN];
@@ -358,30 +377,31 @@
     if (tdbIsSuperTrackChild(tdb))
         {
         // Supertracks have been omitted from fullTrackList, so add the supertrack object's
         // non-parent/child info here.
         jsonWriteObjectStart(jwNew, "parent");
         writeTdbSimple(jwNew, tdb->parent, fieldHash);
         jsonWriteObjectEnd(jwNew);
         }
     else
         // Just the name so we don't have infinite loops.
         jsonWriteString(jwNew, "parent", tdb->parent->track);
     }
 if (doSubtracks)
     {
     jsonWriteListStart(jwNew, "subtracks");
+    slSort(&tdb->subtracks, trackDbViewCmp);
     struct trackDb *subTdb;
     for (subTdb = tdb->subtracks;  subTdb != NULL;  subTdb = subTdb->next)
         {
         struct jsonWrite *jwSub = rTdbToJw(subTdb, fieldHash, excludeTypesHash, depth+1, maxDepth);
         if (jwSub)
             {
             gotSomething = TRUE;
             jsonWriteAppend(jwNew, NULL, jwSub);
             jsonWriteFree(&jwSub);
             }
         }
     jsonWriteListEnd(jwNew);
     }
 jsonWriteObjectEnd(jwNew);
 if (! gotSomething)