f2c11911e71a48636cd19a36231f7b60b43a2764 tdreszer Wed Sep 22 11:38:29 2010 -0700 Bug in js caused many more cart vars to be set than desired. Also made view in browser button reflect whether changes have been made, rather than whether tracks are checked. diff --git src/hg/hgTracks/searchTracks.c src/hg/hgTracks/searchTracks.c index 4cde524..e5f6904 100644 --- src/hg/hgTracks/searchTracks.c +++ src/hg/hgTracks/searchTracks.c @@ -1,833 +1,833 @@ /* Track search code used by hgTracks CGI */ #include "common.h" #include "searchTracks.h" #include "hCommon.h" #include "memalloc.h" #include "obscure.h" #include "dystring.h" #include "hash.h" #include "cheapcgi.h" #include "hPrint.h" #include "htmshell.h" #include "cart.h" #include "hgTracks.h" #include "web.h" #include "jksql.h" #include "hdb.h" #include "trix.h" #include "jsHelper.h" #include "imageV2.h" static char const rcsid[] = "$Id: searchTracks.c,v 1.11 2010/06/11 18:21:40 larrym Exp $"; #define ANYLABEL "Any" #define METADATA_NAME_PREFIX "hgt.metadataName" #define METADATA_VALUE_PREFIX "hgt.metadataValue" static int gCmpGroup(const void *va, const void *vb) /* Compare groups based on label. */ { const struct group *a = *((struct group **)va); const struct group *b = *((struct group **)vb); return strcmp(a->label, b->label); } // Would like to do a radio button choice ofsorts #define SORT_BY_HIERARCHY #ifdef SORT_BY_HIERARCHY #define SORT_BY_VAR "findTracksSortBy" #define SORT_BY_ABC "abc" #define SORT_BY_HIER "hier" static int gCmpTrackHierarchy(const void *va, const void *vb) /* Compare tracks based on longLabel. */ { const struct slRef *aa = *((struct slRef **)va); const struct slRef *bb = *((struct slRef **)vb); const struct track *a = ((struct track *) aa->val); const struct track *b = ((struct track *) bb->val); if ( tdbIsSuperTrack(a->tdb) && !tdbIsSuperTrack(b->tdb)) return -1; else if (!tdbIsSuperTrack(a->tdb) && tdbIsSuperTrack(b->tdb)) return 1; if ( tdbIsComposite(a->tdb) && !tdbIsComposite(b->tdb)) return -1; else if (!tdbIsComposite(a->tdb) && tdbIsComposite(b->tdb)) return 1; if (!tdbIsCompositeChild(a->tdb) && tdbIsCompositeChild(b->tdb)) return -1; else if ( tdbIsCompositeChild(a->tdb) && !tdbIsCompositeChild(b->tdb)) return 1; return strcasecmp(a->longLabel, b->longLabel); } #endif///def SORT_BY_HIERARCHY static int gCmpTrack(const void *va, const void *vb) /* Compare tracks based on longLabel. */ { const struct slRef *aa = *((struct slRef **)va); const struct slRef *bb = *((struct slRef **)vb); const struct track *a = ((struct track *) aa->val); const struct track *b = ((struct track *) bb->val); return strcasecmp(a->longLabel, b->longLabel); } // XXXX make a matchString function to support "contains", "is" etc. and wildcards in contains // ((sameString(op, "is") && !strcasecmp(track->shortLabel, str)) || static boolean isNameMatch(struct track *track, char *str, char *op) { return str && strlen(str) && ((sameString(op, "is") && !strcasecmp(track->shortLabel, str)) || (sameString(op, "is") && !strcasecmp(track->longLabel, str)) || (sameString(op, "contains") && containsStringNoCase(track->shortLabel, str) != NULL) || (sameString(op, "contains") && containsStringNoCase(track->longLabel, str) != NULL)); } static boolean isDescriptionMatch(struct track *track, char **words, int wordCount) // We parse str and look for every word at the start of any word in track description (i.e. google style). { if(words) { // We do NOT lookup up parent hierarchy for html descriptions. char *html = track->tdb->html; if(!isEmpty(html)) { /* This probably could be made more efficient by parsing the html into some kind of b-tree, but I am assuming that the inner html loop while only happen for 1-2 words for vast majority of the tracks. */ int i, numMatches = 0; html = stripRegEx(html, "<[^>]*>", REG_ICASE); for(i = 0; i < wordCount; i++) { char *needle = words[i]; char *haystack, *tmp = cloneString(html); boolean found = FALSE; while((haystack = nextWord(&tmp))) { char *ptr = strstrNoCase(haystack, needle); if(ptr != NULL && ptr == haystack) { found = TRUE; break; } } if(found) numMatches++; else break; } if(numMatches == wordCount) return TRUE; } } return FALSE; } static int getTermArray(struct sqlConnection *conn, char ***terms, char *type) // Pull out all term fields from ra entries with given type // Returns count of items found and items via the terms argument. { struct sqlResult *sr = NULL; char **row = NULL; char query[256]; struct slName *termList = NULL; int i, count = 0; char **retVal; safef(query, sizeof(query), "select distinct val from metaDb where var = '%s'", type); sr = sqlGetResult(conn, query); while ((row = sqlNextRow(sr)) != NULL) { slNameAddHead(&termList, row[0]); count++; } sqlFreeResult(&sr); slSort(&termList, slNameCmpCase); count++; // make room for "Any" AllocArray(retVal, count); retVal[0] = cloneString(ANYLABEL); for(i = 1; termList != NULL;termList = termList->next, i++) { retVal[i] = cloneString(termList->name); } *terms = retVal; return count; } static struct slName *metaDbSearch(struct sqlConnection *conn, char *name, char *val, char *op) // Search the assembly's metaDb table for var; If name == NULL, we search every metadata field. // Search is via mysql, so it's case-insensitive. { char query[256]; char *prefix = "select distinct obj from metaDb"; if(sameString(op, "contains")) if(name == NULL) safef(query, sizeof(query), "%s where val like '%%%s%%'", prefix, val); else safef(query, sizeof(query), "%s where var = '%s' and val like '%%%s%%'", prefix, name, val); else if(name == NULL) safef(query, sizeof(query), "%s where val = '%s'", prefix, val); else safef(query, sizeof(query), "%s where var = '%s' and val = '%s'", prefix, name, val); return sqlQuickList(conn, query); } static int metaDbVars(struct sqlConnection *conn, char *** metaVars, char *** metaLabels) // Search the assemblies metaDb table; If name == NULL, we search every metadata field. { char query[256]; #define WHITE_LIST_COUNT 35 #ifdef WHITE_LIST_COUNT #define WHITE_LIST_VAR 0 #define WHITE_LIST_LABEL 1 char *whiteList[WHITE_LIST_COUNT][2] = { {"age", "Age of experimental organism"}, {"antibody", "Antibody or target protein"}, {"origAssembly", "Assembly originally mapped to"}, {"cell", "Cell, tissue or DNA sample"}, {"localization", "Cell compartment"}, {"control", "Control or Input for ChIPseq"}, //{"controlId", "ControlId - explicit relationship"}, {"dataType", "Experiment type"}, {"dataVersion", "ENCODE release"}, //{"fragLength", "Fragment Length for ChIPseq"}, //{"freezeDate", "Gencode freeze date"}, //{"level", "Gencode level"}, //{"annotation", "Gencode annotation"}, {"accession", "GEO accession"}, {"growthProtocol", "Growth Protocol"}, {"lab", "Lab producing data"}, {"labVersion", "Lab specific details"}, {"labExpId", "Lab specific identifier"}, {"softwareVersion", "Lab specific informatics"}, {"protocol", "Library Protocol"}, {"mapAlgorithm", "Mapping algorithm"}, {"readType", "Paired/Single reads lengths"}, {"grant", "Prinipal Investigator"}, {"replicate", "Replicate number"}, //{"restrictionEnzyme","Restriction Enzyme used"}, //{"ripAntibody", "RIP Antibody"}, //{"ripTgtProtein", "RIP Target Protein"}, {"rnaExtract", "RNA Extract"}, {"seqPlatform", "Sequencing Platform"}, {"setType", "Experiment or Input"}, {"sex", "Sex of organism"}, {"strain", "Strain of organism"}, {"subId", "Submission Id"}, {"treatment", "Treatment"}, {"view", "View - Peaks or Signals"}, }; // FIXME: The whitelist should be a table or ra // FIXME: The whitelist should be in list order // FIXME: Should read in list, then verify that an mdb val exists. char **retVar = needMem(sizeof(char *) * WHITE_LIST_COUNT); char **retLab = needMem(sizeof(char *) * WHITE_LIST_COUNT); int ix,count; for(ix=0,count=0;ix<WHITE_LIST_COUNT;ix++) { safef(query, sizeof(query), "select count(*) from metaDb where var = '%s'",whiteList[ix][WHITE_LIST_VAR]); if(sqlQuickNum(conn,query) > 0) { retVar[count] = whiteList[ix][WHITE_LIST_VAR]; retLab[count] = whiteList[ix][WHITE_LIST_LABEL]; count++; } } if(count == 0) { freez(&retVar); freez(&retLab); } *metaVars = retVar; *metaLabels = retLab; return count; #else///ifndef WHITE_LIST_COUNT char **retVar; char **retLab; struct slName *el, *varList = NULL; struct sqlResult *sr = NULL; char **row = NULL; safef(query, sizeof(query), "select distinct var from metaDb order by var"); sr = sqlGetResult(conn, query); while ((row = sqlNextRow(sr)) != NULL) slNameAddHead(&varList, row[0]); sqlFreeResult(&sr); retVar = needMem(sizeof(char *) * slCount(varList)); retLab = needMem(sizeof(char *) * slCount(varList)); slReverse(&varList); //slNameSort(&varList); int count = 0; for (el = varList; el != NULL; el = el->next) { retVar[count] = el->name; retLab[count] = el->name; count++; } *metaVars = retVar; *whiteLabels = retLab; return count; #endif///ndef WHITE_LIST_COUNT } void doSearchTracks(struct group *groupList) { struct group *group; char *groups[128]; char *labels[128]; int numGroups = 1; groups[0] = ANYLABEL; labels[0] = ANYLABEL; char *currentTab = cartUsualString(cart, "hgt.currentSearchTab", "simpleTab"); char *nameSearch = cartOptionalString(cart, "hgt.nameSearch"); char *descSearch; char *groupSearch = cartOptionalString(cart, "hgt.groupSearch"); boolean doSearch = sameString(cartOptionalString(cart, searchTracks), "Search") || cartUsualInt(cart, "hgt.forceSearch", 0) == 1; struct sqlConnection *conn = hAllocConn(database); boolean metaDbExists = sqlTableExists(conn, "metaDb"); struct slRef *tracks = NULL; int numMetadataSelects, tracksFound = 0; int numMetadataNonEmpty = 0; char **mdbVar; char **mdbVal; struct hash *parents = newHash(4); boolean simpleSearch; struct trix *trix; char trixFile[HDB_MAX_PATH_STRING]; char **descWords = NULL; int descWordCount = 0; boolean searchTermsExist = FALSE; if(sameString(currentTab, "simpleTab")) { descSearch = cartOptionalString(cart, "hgt.simpleSearch"); simpleSearch = TRUE; freez(&nameSearch); freez(&groupSearch); } else { descSearch = cartOptionalString(cart, "hgt.descSearch"); simpleSearch = FALSE; } getSearchTrixFile(database, trixFile, sizeof(trixFile)); trix = trixOpen(trixFile); getTrackList(&groupList, -2); slSort(&groupList, gCmpGroup); for (group = groupList; group != NULL; group = group->next) { if (group->trackList != NULL) { groups[numGroups] = cloneString(group->name); labels[numGroups] = cloneString(group->label); numGroups++; if (numGroups >= ArraySize(groups)) internalErr(); } } webStartWrapperDetailedNoArgs(cart, database, "", "Search for Tracks", FALSE, FALSE, FALSE, FALSE); hPrintf("<div style='max-width:1000px'>"); hPrintf("<form action='%s' name='SearchTracks' id='searchTracks' method='get'>\n\n", hgTracksName()); cartSaveSession(cart); // Creates hidden var of hgsid to avoid bad voodoo hPrintf("<input type='hidden' name='db' value='%s'>\n", database); hPrintf("<input type='hidden' name='hgt.currentSearchTab' id='currentSearchTab' value='%s'>\n", currentTab); hPrintf("<input type='hidden' name='hgt.delRow' value=''>\n"); hPrintf("<input type='hidden' name='hgt.addRow' value=''>\n"); hPrintf("<input type='hidden' name='hgt.forceSearch' value=''>\n"); hPrintf("<div id='tabs' style='display:none; width:inherit'>\n" "<ul>\n" "<li><a href='#simpleTab'><span>Search</span></a></li>\n" "<li><a href='#advancedTab'><span>Advanced</span></a></li>\n" "</ul>\n" "<div id='simpleTab' style='width:inherit;'>\n"); hPrintf("<table style='width:100%%;'><tr><td colspan='2'>"); hPrintf("<input type='text' name='hgt.simpleSearch' id='simpleSearch' value='%s' style='width:100%%' onkeyup='findTracksSearchButtonsEnable(true);'>\n", descSearch == NULL ? "" : descSearch); hPrintf("</td></tr><tr><td>"); if (simpleSearch && descSearch) searchTermsExist = TRUE; hPrintf("<input type='submit' name='%s' id='searchSubmit' value='Search' style='font-size:14px;'>\n", searchTracks); hPrintf("<input type='button' name='clear' value='Clear' class='clear' style='font-size:14px;' onclick='findTracksClear();'>\n"); hPrintf("<input type='submit' name='submit' value='Cancel' class='cancel' style='font-size:14px;'>\n"); hPrintf("<a target='_blank' href='../goldenPath/help/trackSearch.html'>help</a></td></tr></table>\n"); //hPrintf("</td><td align='right'><a target='_blank' href='../goldenPath/help/trackSearch.html'>help</a></td></tr></table>\n"); hPrintf("</div>\n" "<div id='advancedTab'>\n" "<table cellSpacing=0 style='width:100%%;'>\n"); // Track Name contains hPrintf("<tr><td colspan=3 style='width:5%%;'></td>"); hPrintf("<td style='width:10px;' nowrap><b>Track Name:</b></td>"); hPrintf("<td align='right' style='width:10px;'>contains</td>\n"); hPrintf("<td colspan=3>"); hPrintf("<input type='text' name='hgt.nameSearch' id='nameSearch' value='%s' onkeyup='findTracksSearchButtonsEnable(true);' style='min-width:326px;'>", nameSearch == NULL ? "" : nameSearch); hPrintf("</td></tr>\n"); if (!simpleSearch && nameSearch) searchTermsExist = TRUE; // Description contains hPrintf("<tr><td colspan=2 style='width:2%%;'></td><td style='width:2%%;'>and </td>"); hPrintf("<td style='width:10px;'><b>Description:</b></td>"); hPrintf("<td align='right' style='width:10px;'>contains</td>\n"); hPrintf("<td colspan=3>"); hPrintf("<input type='text' name='hgt.descSearch' id='descSearch' value='%s' onkeyup='findTracksSearchButtonsEnable(true);' style='min-width:100%%;'>", descSearch == NULL ? "" : descSearch); hPrintf("</td></tr>\n"); if (!simpleSearch && descSearch) searchTermsExist = TRUE; // Group is hPrintf("<tr><td colspan=2 style='width:2%%;'></td><td style='width:2%%;'>and </td>\n"); hPrintf("<td style='width:10px;'><b>Group</b></td>"); hPrintf("<td align='right' style='width:10px;'>is</td>\n"); hPrintf("<td colspan=3>"); cgiMakeDropListFull("hgt.groupSearch", labels, groups, numGroups, groupSearch, "class='groupSearch' style='min-width:40%%;'"); hPrintf("</td></tr>\n"); if (!simpleSearch && groupSearch) searchTermsExist = TRUE; // figure out how many metadata selects are visible. int delSearchSelect = cartUsualInt(cart, "hgt.delRow", 0); // 1-based row to delete int addSearchSelect = cartUsualInt(cart, "hgt.addRow", 0); // 1-based row to insert after for(numMetadataSelects = 0;;) { char buf[256]; safef(buf, sizeof(buf), "%s%d", METADATA_NAME_PREFIX, numMetadataSelects + 1); char *str = cartOptionalString(cart, buf); if(isEmpty(str)) break; else numMetadataSelects++; } if(delSearchSelect) numMetadataSelects--; if(addSearchSelect) numMetadataSelects++; if(numMetadataSelects) { mdbVar = needMem(sizeof(char *) * numMetadataSelects); mdbVal = needMem(sizeof(char *) * numMetadataSelects); int i; for(i = 0; i < numMetadataSelects; i++) { char buf[256]; int offset; // used to handle additions/deletions if(addSearchSelect > 0 && i >= addSearchSelect) offset = 0; // do nothing to offset (i.e. copy data from previous row) else if(delSearchSelect > 0 && i + 1 >= delSearchSelect) offset = 2; else offset = 1; safef(buf, sizeof(buf), "%s%d", METADATA_NAME_PREFIX, i + offset); mdbVar[i] = cloneString(cartOptionalString(cart, buf)); if(!simpleSearch) { safef(buf, sizeof(buf), "%s%d", METADATA_VALUE_PREFIX, i + offset); mdbVal[i] = cloneString(cartOptionalString(cart, buf)); if(sameString(mdbVal[i], ANYLABEL)) mdbVal[i] = NULL; if(!isEmpty(mdbVal[i])) numMetadataNonEmpty++; } } if(delSearchSelect > 0) { char buf[255]; safef(buf, sizeof(buf), "%s%d", METADATA_NAME_PREFIX, numMetadataSelects + 1); cartRemove(cart, buf); safef(buf, sizeof(buf), "%s%d", METADATA_VALUE_PREFIX, numMetadataSelects + 1); cartRemove(cart, buf); } } else { // create defaults numMetadataSelects = 2; mdbVar = needMem(sizeof(char *) * numMetadataSelects); mdbVal = needMem(sizeof(char *) * numMetadataSelects); mdbVar[0] = "cell"; mdbVar[1] = "antibody"; mdbVal[0] = ANYLABEL; mdbVal[1] = ANYLABEL; } if(metaDbExists) { int i; char **mdbVars = NULL; char **mdbVarLabels = NULL; int count = metaDbVars(conn, &mdbVars,&mdbVarLabels); hPrintf("<tr cellpadding=0><td colspan=10 class='lineOnBottom' style='height:2px; max-height:2px;'> </td></tr>"); for(i = 0; i < numMetadataSelects; i++) { char **terms; char buf[256]; int len; hPrintf("<tr><td style='width:2%%;'>\n"); if(numMetadataSelects > 2 || i >= 2) { safef(buf, sizeof(buf), "return delSearchSelect(this, %d);", i + 1); hButtonWithOnClick(searchTracks, "-", "delete this row", buf); } else hPrintf(" "); hPrintf("</td><td style='width:2%%;'>\n"); safef(buf, sizeof(buf), "return addSearchSelect(this, %d);", i + 1); hButtonWithOnClick(searchTracks, "+", "add another row after this row", buf); hPrintf("</td><td style='width:2%%;'>and </td><td colspan=3 nowrap style='width:10%%;'>\n"); safef(buf, sizeof(buf), "%s%i", METADATA_NAME_PREFIX, i + 1); cgiDropDownWithTextValsAndExtra(buf, mdbVarLabels, mdbVars,count,mdbVar[i],"class='mdbVar' onchange=findTracksMdbVarChanged(this)"); hPrintf("</td><td%s>is\n",(i == 0 ? "":" colspan=2")); len = getTermArray(conn, &terms, mdbVar[i]); safef(buf, sizeof(buf), "%s%i", METADATA_VALUE_PREFIX, i + 1); cgiMakeDropListFull(buf, terms, terms, len, mdbVal[i], "class='mdbVal' style='max-width:520px; min-width:200px;' onchange='findTracksSearchButtonsEnable(true)'"); if (!simpleSearch && mdbVal[i]) searchTermsExist = TRUE; if (i == 0) hPrintf("</td><td align='right' valign='top' nowrap><em style='color:%s'>ENCODE terms</em>",COLOR_DARKGREY); hPrintf("</td></tr>\n"); } } hPrintf("<tr><td colspan='10'>\n"); hPrintf("<input type='submit' name='%s' id='searchSubmit' value='Search' style='font-size:14px;'>\n", searchTracks); hPrintf("<input type='button' name='clear' value='Clear' class='clear' style='font-size:14px;' onclick='findTracksClear();'>\n"); hPrintf("<input type='submit' name='submit' value='Cancel' class='cancel' style='font-size:14px;'>\n"); hPrintf("<a target='_blank' href='../goldenPath/help/trackSearch.html'>help</a></td></tr>\n"); hPrintf("</table>\n"); hPrintf("</div>\n</div>\n"); hPrintf("</form>\n"); hPrintf("</div"); // Restricts to max-width:1000px; if(descSearch != NULL && !strlen(descSearch)) descSearch = NULL; if(groupSearch != NULL && sameString(groupSearch, ANYLABEL)) groupSearch = NULL; if(!isEmpty(descSearch)) { char *tmp = cloneString(descSearch); char *val = nextWord(&tmp); struct slName *el, *descList = NULL; int i; while (val != NULL) { slNameAddTail(&descList, val); descWordCount++; val = nextWord(&tmp); } descWords = needMem(sizeof(char *) * descWordCount); for(i = 0, el = descList; el != NULL; i++, el = el->next) descWords[i] = strLower(el->name); } #ifdef SORT_BY_HIERARCHY boolean sortByHierarchy = sameString(cartUsualString(cart,SORT_BY_VAR,SORT_BY_HIER),SORT_BY_HIER); #endif///def SORT_BY_HIERARCHY if(doSearch) { if(simpleSearch) { struct trixSearchResult *tsList; struct hash *trackHash = newHash(0); // Create a hash of tracks, so we can map the track name into a track struct. for (group = groupList; group != NULL; group = group->next) { struct trackRef *tr; for (tr = group->trackList; tr != NULL; tr = tr->next) { struct track *track = tr->track; hashAdd(trackHash, track->track, track); struct track *subTrack = track->subtracks; for (subTrack = track->subtracks; subTrack != NULL; subTrack = subTrack->next) hashAdd(trackHash, subTrack->track, subTrack); } } for(tsList = trixSearch(trix, descWordCount, descWords, TRUE); tsList != NULL; tsList = tsList->next) { struct track *track = (struct track *) hashFindVal(trackHash, tsList->itemId); if (track != NULL) { refAdd(&tracks, track); tracksFound++; } //else // FIXME: Should get to the bottom of why some of these are null // warn("found trix track is NULL."); } #ifdef SORT_BY_HIERARCHY slSort(&tracks, sortByHierarchy? gCmpTrackHierarchy:gCmpTrack); #else///ifndef SORT_BY_HIERARCHY slReverse(&tracks); #endif///ndef SORT_BY_HIERARCHY } else if(!isEmpty(nameSearch) || descSearch != NULL || groupSearch != NULL || numMetadataNonEmpty) { // First do the metaDb searches, which can be done quickly for all tracks with db queries. struct hash *matchingTracks = newHash(0); struct hash *trackMetadata = newHash(0); struct slName *el, *metaTracks = NULL; int i; for(i = 0; i < numMetadataSelects; i++) { if(!isEmpty(mdbVal[i])) { struct slName *tmp = metaDbSearch(conn, mdbVar[i], mdbVal[i], "is"); if(metaTracks == NULL) metaTracks = tmp; else metaTracks = slNameIntersection(metaTracks, tmp); } } for (el = metaTracks; el != NULL; el = el->next) hashAddInt(matchingTracks, el->name, 1); if(metaDbExists && !isEmpty(descSearch)) { // Load all metadata words for each track to facilitate metadata search. char query[256]; struct sqlResult *sr = NULL; char **row; safef(query, sizeof(query), "select obj, val from metaDb"); sr = sqlGetResult(conn, query); while ((row = sqlNextRow(sr)) != NULL) { char *str = cloneString(row[1]); hashAdd(trackMetadata, row[0], str); } sqlFreeResult(&sr); } for (group = groupList; group != NULL; group = group->next) { if(groupSearch == NULL || sameString(group->name, groupSearch)) { if (group->trackList != NULL) { struct trackRef *tr; for (tr = group->trackList; tr != NULL; tr = tr->next) { struct track *track = tr->track; if((isEmpty(nameSearch) || isNameMatch(track, nameSearch, "contains")) && (isEmpty(descSearch) || isDescriptionMatch(track, descWords, descWordCount)) && (!numMetadataNonEmpty || hashLookup(matchingTracks, track->track) != NULL)) { if (track != NULL) { tracksFound++; refAdd(&tracks, track); } else warn("found group track is NULL."); } if (track->subtracks != NULL) { struct track *subTrack; for (subTrack = track->subtracks; subTrack != NULL; subTrack = subTrack->next) { if((isEmpty(nameSearch) || isNameMatch(subTrack, nameSearch, "contains")) && (isEmpty(descSearch) || isDescriptionMatch(subTrack, descWords, descWordCount)) && (!numMetadataNonEmpty || hashLookup(matchingTracks, subTrack->track) != NULL)) { // XXXX to parent hash. - use tdb->parent instead. hashAdd(parents, subTrack->track, track); if (track != NULL) { tracksFound++; refAdd(&tracks, subTrack); } else warn("found subtrack is NULL."); } } } } } } } #ifdef SORT_BY_HIERARCHY slSort(&tracks, sortByHierarchy? gCmpTrackHierarchy:gCmpTrack); #else///ifndef SORT_BY_HIERARCHY slSort(&tracks, gCmpTrack); #endif///ndef SORT_BY_HIERARCHY } } -hPrintf("<div id='foundTracks' style='display:none;'>\n"); // This div allows the clear button to empty it +hPrintf("<div id='found' style='display:none;'>\n"); // This div allows the clear button to empty it if(tracksFound < 1) { if(doSearch) hPrintf("<p>No tracks found</p>\n"); } else { struct hash *tdbHash = makeTrackHash(database, chromName); hPrintf("<form action='%s' name='SearchTracks' id='searchResultsForm' method='post'>\n\n", hgTracksName()); cartSaveSession(cart); // Creates hidden var of hgsid to avoid bad voodoo #define MAX_FOUND_TRACKS 100 if(tracksFound > MAX_FOUND_TRACKS) { hPrintf("<table class='redBox'><tr><td>Found %d tracks, but only the first %d are displayed.",tracksFound,MAX_FOUND_TRACKS); hPrintf("<BR><B><I>Please narrow search criteria to find fewer tracks.</I></B></div></td></tr></table>\n"); } #define ENOUGH_FOUND_TRACKS 10 if(tracksFound >= ENOUGH_FOUND_TRACKS) { hPrintf("<INPUT TYPE=SUBMIT NAME='submit' VALUE='View in Browser' class='viewBtn'>"); - hPrintf(" <FONT class='selCbCount'></font>"); + hPrintf(" <FONT class='selCbCount'></font>\n"); } // Set up json for js functionality struct dyString *jsonTdbVars = NULL; - hPrintf("<table><tr><td colspan='2'>\n"); + hPrintf("<table id='foundTracks'><tr><td colspan='2'>\n"); hPrintf("</td><td align='right'>\n"); #define PM_BUTTON "<IMG height=18 width=18 onclick=\"return findTracksCheckAllWithWait(%s);\" id='btn_%s' src='../images/%s' title='%s all found tracks'>" hPrintf("</td></tr><tr bgcolor='#%s'><td>",HG_COL_HEADER); hPrintf(PM_BUTTON,"true", "plus_all", "add_sm.gif", "Select"); hPrintf(PM_BUTTON,"false","minus_all","remove_sm.gif","Unselect"); hPrintf("</td><td><b>Visibility</b></td><td colspan=2> <b>Track Name</b>\n"); if(tracksFound >= ENOUGH_FOUND_TRACKS) { #ifdef SORT_BY_HIERARCHY hPrintf("<span style='float:right;'>Sort:"); cgiMakeOnClickRadioButton(SORT_BY_VAR, SORT_BY_ABC, !sortByHierarchy,"onchange=\"findTracksSortNow(this);\""); // FIXME: var is in wrong form! hPrintf("Alphabetically"); cgiMakeOnClickRadioButton(SORT_BY_VAR, SORT_BY_HIER,sortByHierarchy, "onchange=\"findTracksSortNow(this);\""); hPrintf("by Hierarchy </span>\n"); #endif///def SORT_BY_HIERARCHY } hPrintf("</td></tr>\n"); int trackCount=0; boolean containerTrackCount = 0; struct slRef *ptr; while((ptr = slPopHead(&tracks))) { if(++trackCount > MAX_FOUND_TRACKS) break; struct track *track = (struct track *) ptr->val; jsonTdbSettingsBuild(&jsonTdbVars, track); #ifdef SORT_BY_HIERARCHY if (tdbIsSuperTrack(track->tdb)) hPrintf("<tr bgcolor='%s' valign='top' class='found'>\n",COLOR_TRACKLIST_LEVEL1); if (tdbIsComposite(track->tdb)) hPrintf("<tr bgcolor='%s' valign='top' class='found'>\n",COLOR_TRACKLIST_LEVEL3); else #endif///def SORT_BY_HIERARCHY hPrintf("<tr bgcolor='%s' valign='top' class='found'>\n",COLOR_TRACKLIST_LEVEL2); hPrintf("<td align='center' valign='center'>\n"); char name[256]; safef(name,sizeof(name),"%s_sel",track->track); boolean checked = FALSE; #define CB_HIDDEN_VAR "<INPUT TYPE=HIDDEN disabled=true NAME='%s_sel' VALUE='%s'>" if(tdbIsCompositeChild(track->tdb)) { checked = fourStateVisible(subtrackFourStateChecked(track->tdb,cart)); // Don't need all 4 states here. Visible=checked&&enabled //track->visibility = limitedVisFromComposite(track); track->visibility = tdbVisLimitedByAncestry(cart, track->tdb, FALSE); checked = (checked && ( track->visibility != tvHide )); // Checked is only if subtrack level vis is also set! // Only subtracks get "_sel" var hPrintf(CB_HIDDEN_VAR,track->track,checked?"1":CART_VAR_EMPTY); } else { track->visibility = tdbVisLimitedByAncestry(cart, track->tdb, FALSE); checked = ( track->visibility != tvHide ); if (tdbIsSuperTrackChild(track->tdb)) hPrintf(CB_HIDDEN_VAR,track->track,checked?"1":CART_VAR_EMPTY); } #define CB_SEEN "<INPUT TYPE=CHECKBOX id='%s_sel_id' VALUE='on' class='selCb' onclick='findTracksClickedOne(this,true);'%s>" hPrintf(CB_SEEN,track->track,(checked?" CHECKED":"")); hPrintf("</td><td>\n"); #define VIS_HIDDEN_VAR "<INPUT TYPE=HIDDEN disabled=true NAME='%s' VALUE='%s'>" hPrintf(VIS_HIDDEN_VAR,track->track,CART_VAR_EMPTY); // All tracks get vis hidden var if (tdbIsSuper(track->tdb)) { // FIXME: Replace this with select box WITHOUT NAME but with id // HOWEVER, I haven't seen a single supertrack in found tracks so I think they are excluded and this is dead code warn("superTrack: %s '%s' doesn't work yet.",track->track,track->longLabel); superTrackDropDown(cart, track->tdb, superTrackHasVisibleMembers(track->tdb)); } else { char extra[512]; safef(extra,sizeof(extra),"id='%s_id' onchange='findTracksChangeVis(this)'",track->track); hTvDropDownClassWithJavascript(NULL, track->visibility,track->canPack,"normalText seenVis",extra); } // If this is a container track, allow configuring... if (tdbIsComposite(track->tdb) || tdbIsSuper(track->tdb)) { containerTrackCount++; hPrintf(" <a href='hgTrackUi?db=%s&g=%s&hgt_searchTracks=1' title='Configure this container track...'>*</a> ",database,track->track); } hPrintf("</td>\n"); //if(tdbIsSuper(track->tdb) || tdbIsComposite(track->tdb)) // hPrintf("<td><a target='_top' href='%s' title='Configure track...'>%s</a></td>\n", trackUrl(track->track, NULL), track->shortLabel); //else hPrintf("<td><a target='_top' onclick=\"hgTrackUiPopUp('%s',true); return false;\" href='%s' title='Display track details'>%s</a></td>\n", track->track, trackUrl(track->track, NULL), track->shortLabel); hPrintf("<td>%s", track->longLabel); compositeMetadataToggle(database, track->tdb, "...", TRUE, FALSE, tdbHash); hPrintf("</td></tr>\n"); } hPrintf("</table>\n"); if(containerTrackCount > 0) hPrintf("* Tracks so marked are containers which group related data tracks. These may not be visible unless further configuration is done. Click on the * to configure these.<BR>\n"); hPrintf("<INPUT TYPE=SUBMIT NAME='submit' VALUE='View in Browser' class='viewBtn'>"); hPrintf(" <FONT class='selCbCount'></font>"); hPrintf("\n</form>\n"); // be done with json hWrites(jsonTdbSettingsUse(&jsonTdbVars)); } if(!doSearch) { hPrintf("<p><b>Recently Done</b><ul>\n" #ifdef SORT_BY_HIERARCHY "<li>Added sort toggle: Alphabetically or by Hierarchy.</li>" #endif///def SORT_BY_HIERARCHY "<li>Composite/view visibilites in hgTrackUi get reshaped to reflect found/selected subtracks. (In demo1: only default state composites; demo2: all composites.)</li>" "<li>Metadata variables have been 'white-listed' to only include vetted items. Short text descriptions and vetted list should be reviewed.</li>" "<li>Clicking on shortLabel for found track will popup the description text. Subtracks should show their composite description.</li>" "<li>Non-data 'container' tracks (composites and supertracks) have '*' to mark them, and can be configured before displaying. Better suggestions?</li>" "<li>Found track list shows only the first 100 tracks with warning to narrow search. Larry suggests this could be done by pages of results in v2.0.</li>\n" "</ul></p>" "<p><b>Suggested improvments:</b><ul>\n" "<li>The metadata values will not be white-listed, but it would be nice to have more descriptive text for them. A short label added to cv.ra?</li>" "<li>Look and feel of found track list (here) and composite subtrack list (hgTrackUi) should converge. Jim suggests look and feel of hgTracks 'Configure Tracks...' list instead.</li>" "<li>Drop-down list of terms (cells, antibodies, etc.) should be multi-select with checkBoxes as seen in filterComposites. Perhaps saved for v2.0.</li>" "</ul></p>\n"); } hPrintf("</div"); // This div allows the clear button to empty it webEndSectionTables(); }