660387765d7bd58729c204580b26bd016b0afeaa
chmalee
  Mon Nov 21 14:30:49 2022 -0800
Do a quick async chromosome name lookup before sending a full search request from hgTracks/hgGateway

diff --git src/hg/js/model/lib/cart.js src/hg/js/model/lib/cart.js
index c02668e..40c91b0 100644
--- src/hg/js/model/lib/cart.js
+++ src/hg/js/model/lib/cart.js
@@ -1,275 +1,280 @@
 // cart: Send cartJson requests to a CGI that returns JSON responses
 
 // This creates a global object, cart, with methods to
 // * set the CGI name to use in the URL
 // * queue up cartJson commands, possibly including cgiVars, optionally with custom callbacks
 // * queue up cartJson commands and upload a file input identified by a JQuery object
 // * (IMPORTANT!) flush the queue; all cgiVars settings from all queued requests have been merged
 //   and are sent along with each queued command object, to avoid cart-saving race conditions
 //   on the server side.
 
 // Typical usage (note: ImModel includes convenient wrapper methods, use those instead
 // if you're subclassing ImModel):
 //
 // The CGI-generated HTML should include an inline script that sets window.hgsid.
 //
 // In model's initialize():
 //     cart.setCgi('hgMyCgi');
 //     cart.send({ getMyInitialState: {} });
 //     cart.flush();
 //
 // In model's UI handler for when user changes some input or clicks on a button:
 //     cart.send({ cgiVar: { someCartVar: newVal },
 //                 doSomething: { what: 'etc' }
 //               },
 //               handleServerResponse, handleServerFailure);
 //
 // In model's UI handler for when user uploads a file:
 //     cart.uploadFile({ cgiVar: { someCartVar: newVal },
 //                       doSomething: { what: 'etc' }
 //                     },
 //                     jqueryFileInput,
 //                     handleServerResponse, handleServerFailure);
 //
 // After all UI handlers have executed (ImModel does this):
 //     cart.flush();
 
 var cart = (function() {
 
     'use strict';
 
     // Private variables:
 
     var cgiBinUrl = '../cgi-bin/';
     // cart.setCgi(name) sets this to cgiBinUrl + name, and must be called before sending requests:
     var cgiUrl;
+    var cgiName; // get sets by cart.setCgi()
 
     // accumulator for cgiVars passed in to send() before flush() is called:
     var cgiVars = {};
 
     // queue of commands from send() and uploadFile(), to send with accumulated cgiVars
     // when flush() is called:
     var requestQueue = [];
 
     // debugging flag for console.log messages
     var debug = false;
 
     // Private functions:
 
     function checkCommandObjType(commandObj) {
         // Make sure commandObj has the correct structure: an object of objects.
         // Throw [message, badValue] otherwise.
         if (! _.isPlainObject(commandObj)) {
 	    throw(['cart: commandObj is not an object', commandObj]);
         }
         // Make sure that commandObj children are objects.
         _.forEach(commandObj, function(value, key) {
 	    if (value && ! _.isPlainObject(value)) {
 	        throw(['cart: commandObj.' + key + ' is not an object', value]);
 	    }
         });
     }
 
     function requireCgiUrl() {
         // Make sure that cgiUrl has been set and return it.
         if (! cgiUrl) {
             throw(['cart.setCgi must be called before attempting to send request']);
         }
         return cgiUrl;
     }
 
     function mergeCgiVars(cgiVarObj) {
         // Merge settings in cgiVarObj with accumulator cgiVars.  Throw if there are
         // any conflicting settings.
         _.merge(cgiVars, cgiVarObj,
             function (oldVal, newVal, setting) {
                 if (! _.isUndefined(oldVal) && oldVal !== newVal) {
                     throw [ 'cart mergeCgiVars: conflicting settings for ' + setting +
                             ': old value = "' + oldVal + '", new value = "' + newVal + '"'];
                 }
             });
     }
 
     function processCommandObj(commandObj) {
         // In preparation for queueing this request, extract cgiVar settings
         // from commandObj and merge them with any settings from other requests
         // that have been queued.  Return a copy of commandObj with cgiVar
         // stripped out (if it's included in commandObj).
         checkCommandObjType(commandObj);
         requireCgiUrl();
         mergeCgiVars(commandObj.cgiVar);
         return _.omit(commandObj, 'cgiVar');
     }
 
     function wrapCommandObj(commandObjNoCgiVar) {
         // Return an object suitable for use as $.ajax's data param, with settings
         // like hgsid and any accumulated settings in cgiVars, and cjCmd set to
         // encoded commandObjNoCgiVar (if non-empty).
         // Throws [message, badValue] if something is not as expected.
         var reqObj = {};
         reqObj.hgsid = window.hgsid;
         if (commandObjNoCgiVar) {
             reqObj.cjCmd = JSON.stringify(commandObjNoCgiVar);
         }
         // Add cart variable settings (if any have been specified) to reqObj.
         // They will be processed by the cart before commands are executed.
         _.assign(reqObj, cgiVars);
         // Add a uniquifier so the browser doesn't use a cached copy:
         reqObj._ = new Date().getTime();
         return reqObj;
     }
     
     function reqToString(reqObj) {
         // Translate ajax request object into a CGI parameter string
         return _.map(reqObj, function(value, key) {
             return key + '=' + encodeURIComponent(value);
         }).join('&');
     }
 
     function defaultErrorCallback(jqXHR, textStatus) {
         // Ignore incomplete requests, likely due to navigating away from the page
         // http://stackoverflow.com/questions/9229005/how-to-handle-jquery-ajax-post-error-when-navigating-away-from-a-page
         if (jqXHR.readyState < 4) {
             return true;
         }
         console.error('Request failed: ', arguments);
         alert('Request failed: ' + textStatus);
     }
 
     function debugLog() {
         // If debug is true, use console.log to print info.
         if (debug) {
             console.log(arguments);
         }
     }
 
     function ajaxParamsForReq(reqObj) {
         // Return an object suitable as the argument to $.ajax for a POST reqObj to the server/CGI.
         var ajaxParams = {
             type: "POST",
             url: requireCgiUrl(),
             data: reqObj,
             dataType: 'json'
         };
         var paramString = reqToString(reqObj);
         debugLog('cart.flush: data =', reqObj, ', params = ' + paramString);
         return ajaxParams;
     }
 
     function ajaxParamsForReqWithFile(reqObj, jqFileInput) {
         // Return an object suitable as the argument to $.ajax for a POST reqObj to the server/CGI
         // with the contents of the file identified by jqFileInput.
         // Depending on whether the browser supports the FormData API, use either FormData
         // or a fallback plugin (jquery.bifrost).
         var ajaxParams = {
             type: 'POST',
             url: requireCgiUrl(),
         };
         var fileInputName = jqFileInput.attr('name');
         var paramString = reqToString(reqObj);
         if (window.FormData) {
             // If running on a modern browser that supports FormData, use that to form
             // the data for the AJAX request:
             var formData = new window.FormData();
             formData.append(fileInputName, jqFileInput[0].files[0]);
             _.forEach(reqObj, function(value, key) {
                 formData.append(key, value);
             });
             _.assign(ajaxParams, {
                 data: formData,
                 dataType: 'json',
                 // These two are necessary for JQuery to not interfere with FormData:
                 processData: false,
                 contentType: false
             });
             debugLog('cart.flush: posting FormData for input ' + fileInputName +
                      ', reqObj =', reqObj, ', params = ' + paramString);
         } else {
             // Use JQuery plugin bifrost to upload the file using a hidden iframe as target,
             // in order to support IE <10.  It breaks on IE11 though, go figure.
             _.assign(ajaxParams, {
                 data: reqObj,
                 // Using 'iframe json' here activates jquery.bifrost:
                 dataType: 'iframe json',
                 fileInputs: jqFileInput
             });
             debugLog('cart.flush: using jquery.bifrost plugin for input ' + fileInputName +
                      ', data =', reqObj, ', params = ' + paramString);
         }
         return ajaxParams;
     }
 
     // Return cart object with public methods.
     return {
 
         defaultErrorCallback: function (jqXHR, textStatus) {
             defaultErrorCallback(jqXHR, textStatus);
         },
 
+        cgi: function() {
+            return cgiName;
+        },
 
         setCgi: function(newCgi) {
             // Sets the name of the CGI (e.g. hgIntegrator, hgChooseDb etc).
             // This must be called before cart.send.
+            cgiName = newCgi;
             cgiUrl = cgiBinUrl + newCgi;
         },
 
         send: function(commandObj, successCallback, errorCallback) {
             // Queue up commandObj and callbacks, merging cgiVars with those of othere queued reqs.
             // successCallback and the optional errorCallback are functions(jqXHR, textStatus)
             // Throws [message, badValue] if something is not as expected.
             var cmdObjNoCgiVar = processCommandObj(commandObj);
             // If this request contained only cgiVars (empty cmdObjNoCgiVar) then let those
             // go out with other requests.  Below, flush will make sure that at least one request
             // is sent out if there are cgiVars.
             if (! _.isEmpty(cmdObjNoCgiVar) || successCallback || errorCallback) {
                 requestQueue.push({ commandObj: cmdObjNoCgiVar,
                                     successCallback: successCallback,
                                     errorCallback: errorCallback });
             }
         },
 
         uploadFile: function(commandObj, jqFileInput, successCallback, errorCallback) {
             // Queue up commandObj, jqFileInput and callbacks, merging cgiVars with those
             // of othere queued reqs.
             // successCallback and the optional errorCallback are functions(jqXHR, textStatus)
             // Throws [message, badValue] if something is not as expected.
             var cmdObjNoCgiVar = processCommandObj(commandObj);
             requestQueue.push({ commandObj: cmdObjNoCgiVar,
                                 jqFileInput: jqFileInput,
                                 successCallback: successCallback,
                                 errorCallback: errorCallback });
         },
 
         flush: function() {
             // Use $.ajax to POST queued-up requests with accumulated cgiVars to the server/CGI.
             // If cgiVars have been given, but no cartJson commands, add one empty request to the
             // empty requestQueue so that the cgiVars are sent.
             if (! _.isEmpty(cgiVars) && _.isEmpty(requestQueue)) {
                 requestQueue = [{ commandObj: {} }];
             }
             _.forEach(requestQueue,
                 function(queuedReq) {
                     var reqObj = wrapCommandObj(queuedReq.commandObj);
                     var successCallback = queuedReq.successCallback || _.noop();
                     var errorCallback = queuedReq.errorCallback || defaultErrorCallback;
                     var ajaxParams;
                     if (queuedReq.jqFileInput) {
                         ajaxParams = ajaxParamsForReqWithFile(reqObj, queuedReq.jqFileInput);
                     } else {
                         ajaxParams = ajaxParamsForReq(reqObj);
                     }
                     $.ajax(ajaxParams).done(successCallback).fail(errorCallback);
                 });
             cgiVars = {};
             requestQueue = [];
         },
 
         debug: function(isOn) {
             debug = isOn;
         }
 
     };
 })();
 
 // Without this, jshint complains that cart is not used.  Module system would help.
 cart = cart;