a20e4c95a5fde014130e2d04d61ce814cbe5413b galt Mon May 14 15:25:47 2018 -0700 CIRM facetedSearch diff --git src/hg/lib/tablesTables.c src/hg/lib/tablesTables.c index 92dcada..5375c02 100644 --- src/hg/lib/tablesTables.c +++ src/hg/lib/tablesTables.c @@ -1,77 +1,117 @@ /* tablesTables - this module deals with two types of tables SQL tables in a database, * and fieldedTable objects in memory. It has routines to do sortable, filterable web * displays on tables. */ #include "common.h" #include "hash.h" #include "obscure.h" #include "linefile.h" #include "jksql.h" #include "jsHelper.h" #include "sqlSanity.h" #include "fieldedTable.h" #include "cheapcgi.h" +#include "htmshell.h" #include "web.h" #include "cart.h" +#include "facetField.h" #include "tablesTables.h" struct fieldedTable *fieldedTableFromDbQuery(struct sqlConnection *conn, char *query) /* Return fieldedTable from a database query */ { struct sqlResult *sr = sqlGetResult(conn, query); char **fields; int fieldCount = sqlResultFieldArray(sr, &fields); struct fieldedTable *table = fieldedTableNew(query, fields, fieldCount); char **row; int i = 0; while ((row = sqlNextRow(sr)) != NULL) fieldedTableAdd(table, row, fieldCount, ++i); sqlFreeResult(&sr); return table; } +struct fieldedTable *fieldedTableAndCountsFromDbQuery(struct sqlConnection *conn, char *query, int limit, int offset, + char *selectedFields, struct facetField ***pFfArray, int *pResultCount) +/* Return fieldedTable from a database query and also fetch use and select counts */ +{ +struct sqlResult *sr = sqlGetResult(conn, query); +char **fields; +int fieldCount = sqlResultFieldArray(sr, &fields); +struct facetField **ffArray; +AllocArray(ffArray, fieldCount); +struct fieldedTable *table = fieldedTableNew(query, fields, fieldCount); + +struct facetField *ffList = facetFieldsFromSqlTableInit(fields, fieldCount, selectedFields, ffArray); + +char **row; +int i = 0; +int id = 0; +char *nullVal = "n/a"; // TODO what do we want here? +/* Scan through result saving it in list. */ +while ((row = sqlNextRow(sr)) != NULL) + { + if (perRowFacetFields(fieldCount, row, nullVal, ffArray)) + { + if ((i >= offset) && (i < offset+limit)) + fieldedTableAdd(table, row, fieldCount, ++id); + ++i; + } + } +facetFieldsFromSqlTableFinish(ffList, facetValCmpSelectCountDesc); +sqlFreeResult(&sr); +*pFfArray = ffArray; +*pResultCount = i; +return table; +} + static void showTableFilterInstructionsEtc(struct fieldedTable *table, - char *itemPlural, struct fieldedTableSegment *largerContext, void (*addFunc)(void)) + char *itemPlural, struct fieldedTableSegment *largerContext, void (*addFunc)(void), + char *visibleFacetList) /* Print instructional text, and basic summary info on who passes filter, and a submit * button just in case user needs it */ { /* Print info on matching */ int matchCount = slCount(table->rowList); if (largerContext != NULL) // Need to page? matchCount = largerContext->tableSize; cgiMakeButton("submit", "search"); printf("  "); cgiMakeOnClickButton("clearButton", "$(':input').not(':button, :submit, :reset, :hidden, :checkbox, :radio').val('');\n" "$('[name=cdwBrowseFiles_page]').val('1');\n" "$('#submit').click();\n" , "clear search"); printf("<br>"); printf("%d %s found. ", matchCount, itemPlural); if (addFunc) addFunc(); +if (!visibleFacetList) + { printf("<BR>\n"); printf("You can further filter search results field by field below. "); printf("Wildcard * and ? characters are allowed in text fields. "); printf(">min or <max are allowed in numerical fields.<BR>\n"); } +} static void printSuggestScript(char *id, struct slName *suggestList) /* Print out a little javascript to wrap auto-suggester around control with given ID */ { struct dyString *dy = dyStringNew(256); dyStringPrintf(dy,"$(document).ready(function() {\n"); dyStringPrintf(dy," $('#%s').autocomplete({\n", id); dyStringPrintf(dy," delay: 100,\n"); dyStringPrintf(dy," minLength: 0,\n"); dyStringPrintf(dy," source: ["); char *separator = ""; struct slName *suggest; for (suggest = suggestList; suggest != NULL; suggest = suggest->next) { dyStringPrintf(dy,"%s\"%s\"", separator, suggest->name); @@ -354,70 +394,220 @@ "$('[name=%s_page]').val('%d');\n" "$('#submit').click();\n" , varPrefix, totalPages); } } } } void webFilteredFieldedTable(struct cart *cart, struct fieldedTable *table, char *returnUrl, char *varPrefix, int maxLenField, struct hash *tagOutputWrappers, void *wrapperContext, boolean withFilters, char *itemPlural, int pageSize, struct fieldedTableSegment *largerContext, struct hash *suggestHash, + struct facetField **ffArray, char *visibleFacetList, void (*addFunc)(void)) /* Show a fielded table that can be sorted by clicking on column labels and optionally * that includes a row of filter controls above the labels . * The maxLenField is maximum character length of field before truncation with ... * Pass in 0 for no max */ { if (strchr(returnUrl, '?') == NULL) errAbort("Expecting returnUrl to include ? in showFieldedTable\nIt's %s", returnUrl); -if (withFilters) - showTableFilterInstructionsEtc(table, itemPlural, largerContext, addFunc); +if (withFilters || visibleFacetList) + showTableFilterInstructionsEtc(table, itemPlural, largerContext, addFunc, visibleFacetList); + +printf("<div style='position:relative;'>"); // parent container +int facetMargin = 280; +// right column +printf("<DIV style='position:absolute; top:0; left:%dpx;'>\n", visibleFacetList ? facetMargin : 0); /* Set up our table within table look. */ webPrintLinkTableStart(); /* Draw optional filters cells ahead of column labels*/ if (withFilters) showTableFilterControlRow(table, cart, varPrefix, maxLenField, suggestHash); showTableSortingLabelRow(table, cart, varPrefix, returnUrl); showTableDataRows(table, pageSize, maxLenField, tagOutputWrappers, wrapperContext); /* Get rid of table within table look */ webPrintLinkTableEnd(); if (largerContext != NULL) showTablePaging(table, cart, varPrefix, largerContext, pageSize); +printf("</div>"); + +if (visibleFacetList) // facet desired? + { + // left column + printf("<DIV style='position:absolute; top:2px; left:0; width: %dpx; background-color:#FFFFCC; " + "padding-top:20px; padding-bottom:20px; border: 1px solid grey;'>\n", facetMargin); + + // reset all facet value selections + char *color = "#FFFFCC"; + char *op = "resetAll"; + htmlPrintf("<a href='../cgi-bin/cdwWebBrowse?%s=%s|url|&cdwCommand=browseFiles" + "&browseFiles_facet_op=%s|url|" + "&browseFiles_facet_fieldName=%s|url|" + "&browseFiles_facet_fieldVal=%s|url|" + "&cdwBrowseFiles_page=1' " + "style='display:inline; position:relative; background-color:%s|none|; margin-left:5px;'" + ">%s</a><br>\n", + cartSessionVarName(), cartSessionId(cart), + op, "", "", + color, + "<Clear All" + ); + + int valIndent = 20; + struct slName *nameList = slNameListFromComma(visibleFacetList); + int f; + for (f = 0; f < table->fieldCount; ++f) + { + struct facetField *field = ffArray[f]; + if (slNameInListUseCase(nameList, field->fieldName)) // is this field a visible facet? + { + htmlPrintf("<span style='display:inline; font-weight:bold; margin-left:5px'>%s</span><br>\n", + field->fieldName); // (%d), slCount(field->valList)); + struct facetVal *val; + + if (!field->allSelected) // add reset facet link + { + char *color = "#FFFFCC"; + char *op = "reset"; + htmlPrintf("<a href='../cgi-bin/cdwWebBrowse?%s=%s|url|&cdwCommand=browseFiles" + "&browseFiles_facet_op=%s|url|" + "&browseFiles_facet_fieldName=%s|url|" + "&browseFiles_facet_fieldVal=%s|url|" + "&cdwBrowseFiles_page=1' " + "style='display:inline; position:relative; background-color:%s|none|; margin-left:5px;'" + ">%s</a><br>\n", + cartSessionVarName(), cartSessionId(cart), + op, field->fieldName, "", + color, + "<Clear" + ); + } + + int valuesShown = 0; + for (val = field->valList; val; val=val->next) + { + boolean specificallySelected = (val->selected && !field->allSelected); + if ((val->selectCount > 0 && (field->showAllValues || valuesShown < FacetFieldLimit)) + || specificallySelected) + { + ++valuesShown; + char *color = "#FFFFCC"; + if (val->selected) + color = "#CCFFFF"; + char *op = "add"; + if (specificallySelected) + op = "remove"; + printf("<DIV style='position:relative; left:%dpx; width: %dpx; background-color:#FFFFCC;'>\n", + valIndent, (facetMargin-valIndent)); + htmlPrintf( + "<span " + "style='display:inline; position:relative; background-color:%s|none|;'>" + "<input type=checkbox value=%s class=cdwFSCheckBox %s></span>", + color, + specificallySelected ? "true" : "false", + specificallySelected ? "checked" : ""); + htmlPrintf("<a href='../cgi-bin/cdwWebBrowse?%s=%s|url|&cdwCommand=browseFiles" + "&browseFiles_facet_op=%s|url|" + "&browseFiles_facet_fieldName=%s|url|" + "&browseFiles_facet_fieldVal=%s|url|" + "&cdwBrowseFiles_page=1' " + "style='display:inline; position:relative; background-color:%s|none|;'" + ">", + cartSessionVarName(), cartSessionId(cart), + op, field->fieldName, val->val, + color); + htmlPrintf("%s(%d)</a>\n", val->val, val->selectCount); + printf("</div>\n"); + } + } + + // show See More link when facet has lots of values + if (!(field->showAllValues || valuesShown < FacetFieldLimit)) + { + char *color = "#FFFFCC"; + char *op = "showAllValues"; + htmlPrintf("<a href='../cgi-bin/cdwWebBrowse?%s=%s|url|&cdwCommand=browseFiles" + "&browseFiles_facet_op=%s|url|" + "&browseFiles_facet_fieldName=%s|url|" + "&browseFiles_facet_fieldVal=%s|url|" + "&cdwBrowseFiles_page=1' " + "style='display:inline; position:relative; background-color:%s|none|; margin-left:5px;'" + ">%s</a><br>\n", + cartSessionVarName(), cartSessionId(cart), + op, field->fieldName, "", + color, + "See More" + ); + } + + // show See Less link when facet has lots of values + if (field->showAllValues && valuesShown >= FacetFieldLimit) + { + char *color = "#FFFFCC"; + char *op = "showSomeValues"; + htmlPrintf("<a href='../cgi-bin/cdwWebBrowse?%s=%s|url|&cdwCommand=browseFiles" + "&browseFiles_facet_op=%s|url|" + "&browseFiles_facet_fieldName=%s|url|" + "&browseFiles_facet_fieldVal=%s|url|" + "&cdwBrowseFiles_page=1' " + "style='display:inline; position:relative; background-color:%s|none|; margin-left:5px;'" + ">%s</a><br>\n", + cartSessionVarName(), cartSessionId(cart), + op, field->fieldName, "", + color, + "See Fewer" + ); + } + + + } + } + printf("</div>\n"); + jsInlineF( + "$(function () {\n" + " $('.cdwFSCheckBox').click(function() {\n" + " this.parentElement.nextSibling.click();\n" + " });\n" + "});\n"); + } + +printf("</div>\n"); + } void webSortableFieldedTable(struct cart *cart, struct fieldedTable *table, char *returnUrl, char *varPrefix, int maxLenField, struct hash *tagOutputWrappers, void *wrapperContext) /* Display all of table including a sortable label row. The tagOutputWrappers * is an optional way to enrich output of specific columns of the table. It is keyed * by column name and has for values functions of type webTableOutputWrapperType. */ { webFilteredFieldedTable(cart, table, returnUrl, varPrefix, maxLenField, tagOutputWrappers, wrapperContext, FALSE, NULL, - slCount(table->rowList), NULL, NULL, NULL); + slCount(table->rowList), NULL, NULL, NULL, NULL, NULL); } void webTableBuildQuery(struct cart *cart, char *from, char *initialWhere, char *varPrefix, char *fields, boolean withFilters, struct dyString **retQuery, struct dyString **retWhere) /* Construct select, from and where clauses in query, keeping an additional copy of where * Returns the SQL query and the SQL where expression as two dyStrings (need to be freed) */ { struct dyString *query = dyStringNew(0); struct dyString *where = dyStringNew(0); struct slName *field, *fieldList = commaSepToSlNames(fields); boolean gotWhere = FALSE; sqlDyStringPrintf(query, "select %-s from %-s", sqlCkIl(fields), sqlCkIl(from)); if (!isEmpty(initialWhere)) @@ -493,60 +683,78 @@ if (orderFields[0] == '-') sqlDyStringPrintf(query, " order by %s desc", orderFields+1); else sqlDyStringPrintf(query, " order by %s", orderFields); } // return query and where expression *retQuery = query; *retWhere = where; } void webFilteredSqlTable(struct cart *cart, struct sqlConnection *conn, char *fields, char *from, char *initialWhere, char *returnUrl, char *varPrefix, int maxFieldWidth, struct hash *tagOutWrappers, void *wrapperContext, - boolean withFilters, char *itemPlural, int pageSize, struct hash *suggestHash, void (*addFunc)(void) ) + boolean withFilters, char *itemPlural, int pageSize, struct hash *suggestHash, char *visibleFacetList, void (*addFunc)(void) ) /* Given a query to the database in conn that is basically a select query broken into * separate clauses, construct and display an HTML table around results. This HTML table has * column names that will sort the table, and optionally (if withFilters is set) * it will also allow field-by-field wildcard queries on a set of controls it draws above * the labels. * Much of the functionality rests on the call to webFilteredFieldedTable. This function * does the work needed to bring in sections of potentially huge results sets into * the fieldedTable. */ { struct dyString *query; struct dyString *where; webTableBuildQuery(cart, from, initialWhere, varPrefix, fields, withFilters, &query, &where); -/* Figure out size of query result */ -struct dyString *countQuery = sqlDyStringCreate("select count(*) from %-s", sqlCkIl(from)); -sqlDyStringPrintf(countQuery, "%-s", where->string); // trust -int resultsSize = sqlQuickNum(conn, countQuery->string); -dyStringFree(&countQuery); +char *selectedFacetValues=cartUsualString(cart, "cdwSelectedFieldValues", ""); + +struct facetField **ffArray = NULL; +struct fieldedTable *table = NULL; char pageVar[64]; safef(pageVar, sizeof(pageVar), "%s_page", varPrefix); int page = 0; -struct fieldedTableSegment context = { .tableSize=resultsSize}; -if (resultsSize > pageSize) - { +struct fieldedTableSegment context; page = cartUsualInt(cart, pageVar, 0) - 1; if (page < 0) page = 0; - int lastPage = (resultsSize-1)/pageSize; +context.tableOffset = page * pageSize; + +if (visibleFacetList) + { + table = fieldedTableAndCountsFromDbQuery(conn, query->string, pageSize, context.tableOffset, selectedFacetValues, &ffArray, &context.tableSize); + } +else + { + /* Figure out size of query result */ + struct dyString *countQuery = sqlDyStringCreate("select count(*) from %-s", sqlCkIl(from)); + sqlDyStringPrintf(countQuery, "%-s", where->string); // trust + context.tableSize = sqlQuickNum(conn, countQuery->string); + dyStringFree(&countQuery); + } + +if (context.tableSize > pageSize) + { + int lastPage = (context.tableSize-1)/pageSize; if (page > lastPage) page = lastPage; context.tableOffset = page * pageSize; + } + +if (!visibleFacetList) + { sqlDyStringPrintf(query, " limit %d offset %d", pageSize, context.tableOffset); + table = fieldedTableFromDbQuery(conn, query->string); } -struct fieldedTable *table = fieldedTableFromDbQuery(conn, query->string); webFilteredFieldedTable(cart, table, returnUrl, varPrefix, maxFieldWidth, - tagOutWrappers, wrapperContext, withFilters, itemPlural, pageSize, &context, suggestHash, addFunc); + tagOutWrappers, wrapperContext, withFilters, itemPlural, pageSize, &context, suggestHash, ffArray, visibleFacetList, addFunc); fieldedTableFree(&table); dyStringFree(&query); dyStringFree(&where); }