943c379444cfcb2fd4a59902495851b2ed86f2e1
tdreszer
  Fri Mar 11 16:23:16 2011 -0800
Added filterBy boxes to hgFileUi to allow slimiting the number of files seen on the page
diff --git src/hg/lib/fileUi.c src/hg/lib/fileUi.c
index 70aed8a..877c912 100644
--- src/hg/lib/fileUi.c
+++ src/hg/lib/fileUi.c
@@ -1,27 +1,29 @@
 /* fileUi.c - human genome file downloads common controls. */
 
 #include "common.h"
 #include "hash.h"
 #include "cheapcgi.h"
 #include "jsHelper.h"
 #include "cart.h"
 #include "hdb.h"
 #include "fileUi.h"
 #include "hui.h"
 #include "obscure.h"
 #include "mdb.h"
+#include "jsHelper.h"
+#include "web.h"
 
 // FIXME: Move to hui.h since hui.c also needs this
 #define ENCODE_DCC_DOWNLOADS "encodeDCC"
 
 
 void fileDbFree(struct fileDb **pFileList)
 // free one or more fileDb objects
 {
 while (pFileList && *pFileList)
     {
     struct fileDb *oneFile = slPopHead(pFileList);
 
     freeMem(oneFile->fileName);
     freeMem(oneFile->fileType);
     freeMem(oneFile->fileDate);
@@ -350,30 +352,131 @@
         for(sIx = 0;
             sIx<sortOrder->count && differentString(commonTerms[tIx],sortOrder->column[sIx]);
             sIx++) ;
         if (sIx<sortOrder->count) // Found in sort Order so leave it in mdbObjs
             continue;
 
         char *val = mdbRemoveCommonVar(mdbObjs, commonTerms[tIx]); // All mdbs have it and have the same val for it.
         if (val)
             dyStringPrintf(dyCommon,"%s=%s ",commonTerms[tIx],val);
         }
     return dyStringCannibalize(&dyCommon);
     }
 return NULL;
 }
 
+#define FILTER_THE_FILES
+#ifdef FILTER_THE_FILES
+static char *labelWithVocabLink(char *var,char *title,struct slPair *valsAndLabels)
+/* If the parentTdb has a controlledVocabulary setting and the vocabType is found,
+   then label will be wrapped with the link to all relevent terms.  Return string is cloned. */
+{
+// Determine if the var is cvDefined.  If not, simple link
+boolean cvDefined = FALSE;
+struct hash *cvTypesOfTerms = (struct hash *)mdbCvTermTypeHash();
+if (cvTypesOfTerms != NULL)
+    {
+    struct hash *cvTermDef = hashFindVal(cvTypesOfTerms,var);
+    if (cvTermDef)
+        cvDefined = SETTING_IS_ON(hashFindVal(cvTermDef,"cvDefined"));
+    }
+
+struct dyString *dyLink = dyStringNew(256);
+if (!cvDefined)
+    dyStringPrintf(dyLink,"<A HREF='hgEncodeVocab?type=%s' title='Click for details of \"%s\"' TARGET=ucscVocab>%s</A>",
+                   var,title,title);
+else
+    {
+    dyStringPrintf(dyLink,"<A HREF='hgEncodeVocab?term=");
+    struct slPair *oneVal = valsAndLabels;
+    for(;oneVal!=NULL;oneVal=oneVal->next)
+        {
+        if (oneVal != valsAndLabels)
+            dyStringAppendC(dyLink,',');
+        dyStringAppend(dyLink,mdbPairVal(oneVal));
+        }
+    dyStringPrintf(dyLink,"' title='Click for details of each \"%s\"' TARGET=ucscVocab>%s</A>",title,title);
+    }
+return dyStringCannibalize(&dyLink);
+}
+
+static int filterBoxesForFilesList(char *db,struct mdbObj *mdbObjs,sortOrder_t *sortOrder)
+{  // Will create filterBoxes for each sortOrder field.  Returns count of filterBoxes made
+int count = 0;
+if (sortOrder != NULL)
+    {
+    struct dyString *dyFilters = dyStringNew(256);
+    int sIx=0;
+    for(sIx = 0;sIx<sortOrder->count;sIx++)
+        {
+        char *var = sortOrder->column[sIx];
+        enum mdbCvSearchable searchBy = mdbCvSearchMethod(var);
+        //if (searchBy == cvsSearchByDateRange || searchBy == cvsSearchByIntegerRange) // dates and numbers probably not good for filtering. FIXME: Should cvsNotSearchable be filterable??
+        if (searchBy != cvsSearchBySingleSelect && searchBy != cvsSearchByMultiSelect)
+            continue; // Only single selects and multi-select make good candidates for filtering
+
+        struct sqlConnection *conn = hAllocConn(db);
+        struct slPair *valsAndLabels = mdbValLabelSearch(conn, var, MDB_VAL_STD_TRUNCATION, FALSE, TRUE, TRUE); // tags, yes tables AND files
+        hFreeConn(&conn);
+        // Need to verify that each val exists in an object for these files
+        struct slPair *relevantVals = NULL;
+        while(valsAndLabels != NULL)
+            {
+            struct slPair *oneVal = slPopHead(&valsAndLabels);
+            if(mdbObjsContainAltleastOneMatchingVar(mdbObjs,var,mdbPairVal(oneVal)))
+                {
+                eraseNonAlphaNum(mdbPairVal(oneVal));   // Have to squeeze out uglies from val to ensure filter by class works
+                slAddHead(&relevantVals,oneVal);
+                }
+            else
+                slPairFreeValsAndList(&oneVal);
+            }
+        if (slCount(relevantVals) > 1)
+            {
+            slReverse(&relevantVals);
+            char extraClasses[256];
+            safef(extraClasses,sizeof extraClasses,"filterTable %s",var);
+            char *dropDownHtml = cgiMakeMultiSelectDropList(var,relevantVals,NULL,"All",extraClasses,"onchange='filterTable();' onclick='filterTableExclude(this);'");
+            // Note filterBox has classes: filterBy & {var}
+            if (dropDownHtml)
+                {
+                dyStringPrintf(dyFilters,"<td align='left'>\n%s:<BR>\n%s</td><td width=10>&nbsp;</td>\n",
+                               labelWithVocabLink(var,sortOrder->title[sIx],relevantVals),dropDownHtml);
+                freeMem(dropDownHtml);
+                count++;
+                }
+            }
+        if (slCount(relevantVals) > 0)
+            slPairFreeValsAndList(&relevantVals);
+        }
+    if (count)
+        {
+        webIncludeResourceFile("ui.dropdownchecklist.css");
+        jsIncludeFile("ui.dropdownchecklist.js",NULL);
+        #define FILTERBY_HELP_LINK  "<A HREF=\"../goldenPath/help/multiView.html\" TARGET=ucscHelp>help</A>"
+        printf("<B>Filter files by:</B> (select multiple %sitems - %s)\n<table><tr valign='bottom'>\n",
+               (count >= 1 ? "categories and ":""),FILTERBY_HELP_LINK);
+        printf("%s\n",dyStringContents(dyFilters));
+        printf("</tr></table>\n");
+        printf("<script type='text/javascript'>$(document).ready(function() { $('.filterBy').each( function(i) { $(this).dropdownchecklist({ firstItemChecksAll: true, noneIsAll: true });});});</script>\n");
+        }
+    dyStringFree(&dyFilters);
+    }
+return count;
+}
+#endif///def FILTER_THE_FILES
+
 static void filesDownloadsPreamble(char *db, struct trackDb *tdb)
 {
 // Do not bother getting preamble.html
 // 1) It isn't on the RR (yet)
 // 2) It will likely refer back to the composite for Description info which is included in this page
 // 3) The rsync-to-tmp-file hurdle isn't worth the effort.
 puts("<p><B>Data is <A HREF='http://genome.ucsc.edu/ENCODE/terms.html'>RESTRICTED FROM USE</a>");
 puts("in publication  until the restriction date noted for the given data file.</B></p>");
 
 char *server = hDownloadsServer();
 char *subDir = "";
 if (hIsBetaHost())
     {
     server = "hgdownload-test.cse.ucsc.edu"; // NOTE: Force this case because beta may think it's downloads server is "hgdownload.cse.ucsc.edu"
     subDir = "/beta";
@@ -383,44 +486,45 @@
 if (oneFile != NULL)
     {
     printf("<p>\n<B>Supplemental materials</b> may be found <A HREF='http://%s/goldenPath/%s/%s/%s%s/supplemental/' TARGET=ucscDownloads>here</A>.</p>\n",
           server,db,ENCODE_DCC_DOWNLOADS, tdb->track, subDir);
     }
 puts("<p>\nThere are two files within this directory that contain information about the downloads:");
 printf("<BR>&#149;&nbsp;<A HREF='http://%s/goldenPath/%s/%s/%s%s/files.txt' TARGET=ucscDownloads>files.txt</A> which is a tab-separated file with the name and metadata for each download.</LI>\n",
                 server,db,ENCODE_DCC_DOWNLOADS, tdb->track, subDir);
 printf("<BR>&#149;&nbsp;<A HREF='http://%s/goldenPath/%s/%s/%s%s/md5sum.txt' TARGET=ucscDownloads>md5sum.txt</A> which is a list of the md5sum output for each download.</LI>\n",
                 server,db,ENCODE_DCC_DOWNLOADS, tdb->track, subDir);
 
 
 puts("<P>");
 }
 
-static int filesPrintTable(char *db, struct trackDb *parentTdb, struct fileDb *fileList, sortOrder_t *sortOrder)
+static int filesPrintTable(char *db, struct trackDb *parentTdb, struct fileDb *fileList, sortOrder_t *sortOrder,boolean filterable)
 // Prints filesList as a sortable table. Returns count
 {
 // Table class=sortable
 int columnCount = 0;
 int restrictedColumn = 0;
 char *nowrap = (sortOrder->setting != NULL ? " nowrap":""); // Sort order trackDb setting found so rely on <BR> in titles for wrapping
 printf("<TABLE class='sortable' style='border: 2px outset #006600;'>\n");
 printf("<THEAD class='sortable'>\n");
 printf("<TR class='sortable' valign='bottom'>\n");
 printf("<TD align='center' valign='center'>&nbsp;");
 int filesCount = slCount(fileList);
 if (filesCount > 5)
-    printf("<i>%d files</i>",filesCount);    //puts("<FONT class='subCBcount'></font>"); // Use this style when filterboxes are up and running
+    printf("<em><span class='filesCount'></span>%d files</em>",filesCount);
+
 //if (sortOrder) // NOTE: This could be done to preserve sort order   FIXME: However hgFileUi would need form OR changes would need to be ajaxed over AND hgsid would be needed.
 //    printf("<INPUT TYPE=HIDDEN NAME='%s' class='sortOrder' VALUE=\"%s\">",sortOrder->htmlId, sortOrder->sortOrder);
 printf("</TD>\n");
 columnCount++;
 
 /*#define SHOW_FOLDER_FRO_COMPOSITE_DOWNLOADS
 #ifdef SHOW_FOLDER_FRO_COMPOSITE_DOWNLOADS
 if (parentTdb == NULL)
     {
     printf("<TD align='center' valign='center'>&nbsp;</TD>");
     columnCount++;
     }
 #endif///def SHOW_FOLDER_FRO_COMPOSITE_DOWNLOADS
 */
 // Now the columns
@@ -455,71 +559,82 @@
 // Now the files...
 char *server = hDownloadsServer();
 char *subDir = "";
 if (hIsBetaHost())
     {
     server = "hgdownload-test.cse.ucsc.edu"; // NOTE: Force this case because beta may think it's downloads server is "hgdownload.cse.ucsc.edu"
     subDir = "/beta";
     }
 struct fileDb *oneFile = fileList;
 printf("<TBODY class='sortable sorting'>\n"); // 'sorting' is a fib but it conveniently greys the list till the table is initialized.
 for( ;oneFile!= NULL;oneFile=oneFile->next)
     {
     oneFile->mdb->next = NULL; // mdbs were in list for generating sortOrder, but list no longer needed
     char *field = NULL;
 
-    printf("<TR valign='top'>");   // TODO: BUILD IN THE CLASSES TO ALLOW FILTERBOXES TO WORK!!!
-
+    printf("<TR valign='top'%s>",filterable?" class='filterable'":"");
     // Download button
     printf("<TD nowrap>");
     if (parentTdb)
         field = parentTdb->track;
     else
         field = mdbObjFindValue(oneFile->mdb,"composite");
     assert(field != NULL);
-    printf("<A HREF='http://%s/goldenPath/%s/%s/%s%s/%s' title='Download %s ...' TARGET=ucscDownloads>",
+
+    printf("<input type='button' value='Download' onclick=\"window.location='http://%s/goldenPath/%s/%s/%s%s/%s';\" title='Download %s ...'>",
                 server,db,ENCODE_DCC_DOWNLOADS, field, subDir, oneFile->fileName, oneFile->fileName);
-    printf("<input type='button' value='Download'>");
-    printf("</a>");
 
 #define SHOW_FOLDER_FRO_COMPOSITE_DOWNLOADS
 #ifdef SHOW_FOLDER_FRO_COMPOSITE_DOWNLOADS
     if (parentTdb == NULL)
         printf("&nbsp;<A href='../cgi-bin/hgFileUi?db=%s&g=%s' title='Navigate to downloads page for %s set...'><IMG SRC='../images/folderC.png'></a>&nbsp;", db,field,field);
 #endif///def SHOW_FOLDER_FRO_COMPOSITE_DOWNLOADS
     printf("</TD>\n");
 
     // Each of the pulled out mdb vars
     if (sortOrder)
         {
         for(ix=0;ix<sortOrder->count;ix++)
             {
             char *align = (sameString("labVersion",sortOrder->column[ix]) || sameString("softwareVersion",sortOrder->column[ix]) ? " align='left'":" align='center'");
             if (sameString("fileSize",sortOrder->column[ix]))
                 {
                 char niceNumber[128];
                 sprintWithGreekByte(niceNumber, sizeof(niceNumber), oneFile->fileSize);
                 field = oneFile->sortFields[sortOrder->order[ix] - 1];
                 printf("<TD abbr='%s' align='right' nowrap>%s</td>",field,niceNumber);
                 }
             else
                 {
                 field = oneFile->sortFields[sortOrder->order[ix] - 1];
+                char class[128];
+                class[0] = '\0';
+                if (filterable)
+                    {
+                    enum mdbCvSearchable searchBy = mdbCvSearchMethod(sortOrder->column[ix]);
+                    if (searchBy == cvsSearchBySingleSelect || searchBy == cvsSearchByMultiSelect)
+                        {
+                        char *cleanClass = cloneString(field?field:"None");     // FIXME: Only none if none is a fliter choice.
+                        eraseNonAlphaNum(cleanClass);
+                        safef(class,sizeof class," class='%s %s'",sortOrder->column[ix],cleanClass);
+                        }
+                    }
+
                 if (sameString("dateUnrestricted",sortOrder->column[ix]) && field && dateIsOld(field,"%F"))
-                    printf("<TD%s nowrap style='color: #BBBBBB;'>%s</td>",align,field);
+                    printf("<TD%s nowrap style='color: #BBBBBB;'%s>%s</td>",align,class,field);
                 else
-                    printf("<TD%s nowrap>%s</td>",align,field?field:" &nbsp;");
+                    printf("<TD%s nowrap%s>%s</td>",align,class,field?field:" &nbsp;");
                 if (!sameString("fileType",sortOrder->column[ix]))
                     mdbObjRemoveVars(oneFile->mdb,sortOrder->column[ix]); // Remove this from mdb now so that it isn't displayed in "extras'
                 }
             }
         }
 #ifndef INCLUDE_FILENAMES
     else
 #endif///ndef INCLUDE_FILENAMES
         { // fileName
         printf("<TD nowrap>%s",oneFile->fileName);
         //// FIXME: " The "..." encapsulation could be rebuilt so it could be called here
         //printf("&nbsp;<A HREF='#a_meta_%s' onclick='return metadataShowHide(\"%s\",true,true);' title='Show metadata details...'>...</A>",
         //    oneFile->mdb->obj,oneFile->mdb->obj);
         //printf("<DIV id='div_%s_meta' style='display:none;'></div></td>",oneFile->mdb->obj);
         }
@@ -533,31 +648,31 @@
 
     printf("</TR>\n");
     }
 
 printf("</TBODY><TFOOT class='bgLevel1'>\n");
 printf("<TR valign='top'>");
 
 // Restriction policy link in first column?
 if (restrictedColumn == 1)
     printf("<TH colspan=%d><A HREF='%s' TARGET=BLANK style='font-size:.9em;'>Restriction Policy</A></TH>", (columnCount - restrictedColumn),ENCODE_DATA_RELEASE_POLICY);
 
 printf("<TD colspan=%d>&nbsp;&nbsp;&nbsp;&nbsp;",(restrictedColumn > 1 ? (restrictedColumn - 1) : columnCount));
 
 // Total
 if (filesCount > 5)
-    printf("<i>%d files</i>\n",filesCount);
+    printf("<em><span class='filesCount'></span>%d files</em>",filesCount);
 
 // Restriction policy link in later column?
 if (restrictedColumn > 1)
     printf("</TD><TH colspan=%d align='left'><A HREF='%s' TARGET=BLANK style='font-size:.9em;'>Restriction Policy</A>", columnCount,ENCODE_DATA_RELEASE_POLICY);
 
 printf("</TD></TR>\n");
 printf("</TFOOT></TABLE><BR>\n");
 
 if (parentTdb == NULL)
     printf("<script type='text/javascript'>{$(document).ready(function() {sortTableInitialize($('table.sortable')[0],true,true);});}</script>\n");
 
 return filesCount;
 }
 
 
@@ -573,46 +688,47 @@
     // 3) Verification of each file in its discovered location
     // 4) Lookup of 'fileSortOrder'
     // 5) TODO: present filter controls
     // 6) Presort of files list
     // 7) make table class=sortable
     // 8) Final file count
     // 9) Use trackDb settings to get at html description
     // Nice to have: Make filtering and sorting persistent (saved to cart)
 
 // FIXME: Trick while developing:
 if (tdb->table != NULL)
     tdb->track = tdb->table;
 
 boolean debug = cartUsualBoolean(cart,"debug",FALSE);
 
-struct sqlConnection *conn = sqlConnect(db);
+struct sqlConnection *conn = hAllocConn(db);
 char *mdbTable = mdbTableName(conn,TRUE); // Look for sandBox name first
 if(mdbTable == NULL)
     errAbort("TABLE NOT FOUND: '%s.%s'.\n",db,MDB_DEFAULT_NAME);
 
 // Get an mdbObj list of all that belong to this track and have a fileName
 char buf[256];
 safef(buf,sizeof(buf),"composite=%s fileName=?",tdb->track);
 struct mdbByVar *mdbVars = mdbByVarsLineParse(buf);
 struct mdbObj *mdbList = mdbObjsQueryByVars(conn,mdbTable,mdbVars);
 
 // Now get Indexes  But be sure not to duplicate entries in the list!!!
 safef(buf,sizeof(buf),"composite=%s fileIndex= fileName!=",tdb->track);
 mdbVars = mdbByVarsLineParse(buf);
 mdbList = slCat(mdbList, mdbObjsQueryByVars(conn,mdbTable,mdbVars));
-sqlDisconnect(&conn);
+mdbObjRemoveHiddenVars(mdbList);
+hFreeConn(&conn);
 
 if (slCount(mdbList) == 0)
     {
     warn("No files specified in metadata for: %s\n%s",tdb->track,tdb->longLabel);
     return;
     }
 
 // Verify file existance and make fileList of those found
 struct fileDb *fileList = NULL, *oneFile = NULL; // Will contain found files
 struct mdbObj *mdbFiles = NULL; // Will caontain a list of mdbs for the found files
 while(mdbList)
     {
     char buf[512];
     boolean found = FALSE;
     struct mdbObj *mdbFile = slPopHead(&mdbList);
@@ -661,81 +777,82 @@
     if (!found)
         mdbObjsFree(&mdbFile);
     }
 
 if (slCount(fileList) == 0)
     {
     warn("No downloadable files currently available for: %s\n%s",tdb->track,tdb->longLabel);
     return;  // No files so nothing to do.
     }
 if (debug)
     {
     warn("The following files are in goldenPath/%s/%s/%s/ but NOT in the mdb:",db,ENCODE_DCC_DOWNLOADS, tdb->track);
     fileDbGet(db, ENCODE_DCC_DOWNLOADS, tdb->track, "listAll");
     }
 
+jsIncludeFile("hui.js",NULL);
+jsIncludeFile("ajax.js",NULL);
+
+// standard preamble
+filesDownloadsPreamble(db,tdb);
+
 // Now update all files with their sortable fields and sort the list
 sortOrder_t *sortOrder = fileSortOrderGet(cart,tdb,mdbFiles);
+boolean filterable = FALSE;
 if (sortOrder != NULL)
     {
     char *vars = removeCommonMdbVarsNotInSortOrder(mdbFiles,sortOrder);
     if (vars)
         {
         if (debug)
             warn("These terms are common:%s",vars);
         freeMem(vars);
         }
 
     // Fill in and sort fileList
     fileDbSortList(&fileList,sortOrder);
-    }
-
 // FilterBoxes ?
-    // Dimensions
-    // cart contents
-    //boolean filterAble = dimensionsExist(tdb);
-    //membersForAll_t* membersForAll = membersForAllSubGroupsGet(tdb,cart);
+#ifdef FILTER_THE_FILES
+    filterable = (filterBoxesForFilesList(db,mdbFiles,sortOrder) > 0);
+#endif///def FILTER_THE_FILES
 
-jsIncludeFile("hui.js",NULL);
-jsIncludeFile("ajax.js",NULL);
-
-// standard preamble
-filesDownloadsPreamble(db,tdb);
+    }
 
 // Print table
-filesPrintTable(db,tdb,fileList,sortOrder);
+filesPrintTable(db,tdb,fileList,sortOrder,filterable);
 
 //fileDbFree(&fileList); // Why bother on this very long running cgi?
 }
 
 int fileSearchResults(char *db, struct sqlConnection *conn, struct slPair *varValPairs, char *fileType)
 // Prints list of files in downloads directories matching mdb search terms. Returns count
 {
 struct sqlConnection *connLocal = conn;
 if (conn == NULL)
     connLocal = hAllocConn(db);
 struct mdbObj *mdbList = mdbObjRepeatedSearch(connLocal,varValPairs,FALSE,TRUE);
 if (conn == NULL)
     hFreeConn(&connLocal);
 if (slCount(mdbList) == 0)
     {
     printf("<DIV id='filesFound'><BR>No files found.<BR></DIV><BR>\n");
     return 0;
     }
 
 // Now sort mdbObjs so that composites will stay together and lookup of files will be most efficient
 mdbObjsSortOnVars(&mdbList, "composite");
+mdbObjRemoveHiddenVars(mdbList);
 
 #define FOUND_FILE_LIMIT 1000
 int fileCount = 0;
 // Verify file existance and make fileList of those found
 struct fileDb *fileList = NULL, *oneFile = NULL; // Will contain found files
 struct mdbObj *mdbFiles = NULL; // Will caontain a list of mdbs for the found files
 while(mdbList && fileCount < FOUND_FILE_LIMIT)
     {
     boolean found = FALSE;
     struct mdbObj *mdbFile = slPopHead(&mdbList);
     char *composite = mdbObjFindValue(mdbFile,"composite");
     if (composite != NULL)
         {
         // First for FileName
         char *fileName = mdbObjFindValue(mdbFile,"fileName");
@@ -809,22 +926,22 @@
 mdbObjRemoveVars(mdbFiles,"tableName"); // Remove this from mdb now so that it isn't displayed in "extras'
 
 //jsIncludeFile("hui.js",NULL);
 //jsIncludeFile("ajax.js",NULL);
 
 // Print table
 printf("<DIV id='filesFound'>");
 if (mdbList != NULL)
     {
     printf("<DIV class='redBox' style='width: 380px;'>Too many files found.  Displaying first %d of potentially %d.<BR>Narrow search parameters and try again.</DIV><BR>\n",
            fileCount,(fileCount+slCount(mdbList)*2)); // Multiply*2 because of fileIndexes
     //warn("Too many files found.  Displaying first %d of potentially %d.<BR>Narrow search parameters and try again.\n", fileCount,(fileCount+slCount(mdbList)*2)); // Multiply because of fileIndexes
     mdbObjsFree(&mdbList);
     }
 
-fileCount = filesPrintTable(db,NULL,fileList,sortOrder);
+fileCount = filesPrintTable(db,NULL,fileList,sortOrder,FALSE); // FALSE=Don't offer more filtering on the file search page
 printf("</DIV><BR>\n");
 
 //fileDbFree(&fileList); // Why bother on this very long running cgi?
 return fileCount;
 }