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> </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>• <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>• <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'> "); 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'> </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(" <A href='../cgi-bin/hgFileUi?db=%s&g=%s' title='Navigate to downloads page for %s set...'><IMG SRC='../images/folderC.png'></a> ", 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:" "); + printf("<TD%s nowrap%s>%s</td>",align,class,field?field:" "); 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(" <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> ",(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; }