403ab7c9c2b204f487bb2f86260ffdab355e9517
jcasper
  Wed Aug 19 05:49:25 2026 -0700
Faceted composites should apply the active sort order to the tracks being
displayed; changing the sort changes the display order.  We also preserve that order when
returning to the page.  refs #36320

diff --git src/hg/js/utils.js src/hg/js/utils.js
index 8a3ab88fa1a..527b53e3ead 100644
--- src/hg/js/utils.js
+++ src/hg/js/utils.js
@@ -3078,31 +3078,31 @@
                     error: errorHandler,
                     cache: true,
                     cmd: "hgt_mdbVal" + num, // NOTE must match METADATA_VALUE_PREFIX
                     num: num                 //      in hg/hgTracks/searchTracks.c
                 });
         }
         // NOTE: with newJquery, the response is getting a new error (missing ; before statement)
         //       There were also several XML parsing errors.
         // This error is fixed with the addition of "dataType: 'html'," above.
     },
 
     handleNewMdbVals: function (response, status)
     {   // Handle ajax response (repopulate a metadata val select)
         // This handles the currnet case when 2 vars have the same name (e.g. advanced, files tabs)
 
-        var td = normed($('td#' + this.cmd));
+        var td = normed($('td#' + this.cmd + '_td'));
         if (td) {
             $(td).empty();
             $(td).append(response);
             var inp = normed($(td).find('.mdbVal'));
             var tdIsLike = normed($('td#isLike'+this.num));
             if (inp && tdIsLike) {
                 if ($(inp).hasClass('freeText')) {
                     $(tdIsLike).text('contains');
                 } else if ($(inp).hasClass('wildList') ||  $(inp).hasClass('filterBy')) {
                     $(tdIsLike).text('is among');
                 } else {
                     $(tdIsLike).text('is');
                 }
             }
             // Do this by 'each' to set noneIsAll individually
@@ -3127,30 +3127,46 @@
             if (newVal !== undefined && newVar !== null && a && a[1]) {
                 var num = a[1];
                 $("input.mdbVal[name='hgt_mdbVal"+num+"'][value!='"+newVal+"']").val(newVal);
                 $("select.mdbVal[name='hgt_mdbVal"+num+"'][value!='"+newVal+"']").each( function (i) {
                     $(this).val(newVal);
                     if ($(this).hasClass('filterBy')) {
                         $(this).dropdownchecklist("destroy");
                         ddcl.setup(this,'noneIsAll');
                     }
                 });
             }
         }
         //findTracks.searchButtonsEnable(true);
     },
 
+    initMdbFilters: function ()
+    {   // Wire up mdb var/val change handling via delegation on the document, so that rows
+        // added later by the [+] button (which clones a row, losing its event handlers) are
+        // covered without re-binding.  Uses jQuery delegation rather than a native
+        // addEventListener: the filterBy multiselects fire their 'change' through jQuery
+        // (ui.dropdownchecklist.js), which a native document listener would not catch.
+        // Called once at page load from the track search and file search pages.
+        if (findTracks.mdbFiltersInited)
+            return;
+        findTracks.mdbFiltersInited = true;
+        $(document).on('change', 'select.mdbVar', function () { findTracks.mdbVarChanged(this); });
+        $(document).on('change', 'select.mdbVal', function () { findTracks.mdbValChanged(this); });
+        // freeText/wildList vals are <input>s; their change just abandons found results
+        $(document).on('change', 'input.mdbVal',  function () { findTracks.mdbVarChanged(this); });
+    },
+
     changeVis: function (seenVis)
     {   // called by onchange of vis
         var visName = $(seenVis).attr('id');
         var trackName = visName.substring(0,visName.length - "_id".length);
         var hiddenVis = $("input[name='"+trackName+"']");
         var tdb = tdbGetJsonRecord(trackName);
         if ($(seenVis).val() !== "hide")
             $(hiddenVis).val($(seenVis).val());
         else {
             var selCb = $("input#"+trackName+"_sel_id");
             $(selCb).attr('checked',false);    // Can't set these to [] because that means default
             $(seenVis).attr('disabled',true);  // setting is used. However, we're explicitly hiding!
             var needSel = (tdb.parentTrack !== undefined && tdb.parentTrack !== null);
             if (needSel) {
                 var hiddenSel = $("input[name='"+trackName+"_sel']");
@@ -3500,31 +3516,31 @@
                 element = $(this).find(".mdbVal")[0];      // select val
                 if (element) {                // not there if new row
                     $(element).attr('name','hgt_mdbVal' + rowNum);
                     if ($(element).hasClass('filterBy')) {
                         $(element).attr('id',''); // removing id ensures renumbering id
                         ddcl.reinit([ element ],true);
                     }
                 }
 
                 // A couple more things
                 element = $(this).find("td[id^='isLike']")[0];
                 if (element)
                     $(element).attr('id','isLike' + rowNum);
                 element = $(this).find("td[id^='hgt_mdbVal']")[0];
                 if (element)
-                    $(element).attr('id','hgt_mdbVal' + rowNum);
+                    $(element).attr('id','hgt_mdbVal' + rowNum + '_td');
             });
 
             return mdbSelectRows.length;
         }
         return 0;
     },
 
     switchTabs: function (ui)
     {   // switching tabs on findTracks page
 
         id = ui.newPanel[0].id;
         if (id === 'simpleTab' && $('div#found').length < 1) {
             // delay necessary, since select event not afterSelect event
             setTimeout(function() { 
                             $('input#simpleSearch').focus();