0002cfbdde2e145cf945fba9a26c6e60ea3cd666
kent
Thu Jan 28 18:08:55 2021 -0800
Quite a bit of refactoring to help make this work inside a genome browser page. Still a bit to go. In particular have made the facet count a parameter, and have made the buttons associated with a search optional to display. Also facets no longer have to be visible fields. I'm hoping I have not broken CIRM CDW compatibility but have not tested sufficiently.
diff --git src/hg/lib/tablesTables.c src/hg/lib/tablesTables.c
index 77e170c..8fffdb5 100644
--- src/hg/lib/tablesTables.c
+++ src/hg/lib/tablesTables.c
@@ -4,103 +4,104 @@
#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"
+#include "csv.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)
+ char *visibleFields, 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);
+struct facetField *ffList = facetFieldsFromSqlTableInit(fields, fieldCount, visibleFields, ffArray);
char **row;
int i = 0;
int id = 0;
char *nullVal = "n/a";
/* 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)(int),
+ char *pluralInstructions, struct fieldedTableSegment *largerContext, void (*addFunc)(int),
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;
printf("");
printf("  ");
printf("");
jsOnEventById("click", "clearButton",
"$(':input').not(':button, :submit, :reset, :hidden, :checkbox, :radio').val('');\n"
"$('[name=cdwBrowseFiles_page]').val('1');\n"
"$('#submit').click();\n");
printf(" ");
-printf("%d %s found. ", matchCount, itemPlural);
+printf("%d %s found. ", matchCount, pluralInstructions);
if (addFunc)
addFunc(matchCount);
if (!visibleFacetList)
{
printf(" \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. \n");
}
}
static void printSuggestScript(char *id, struct slName *suggestList)
/* Print out a little javascript to wrap auto-suggester around control with given ID */
@@ -137,51 +138,52 @@
#endif
static void resetPageNumberOnChange(char *id)
/* On change, reset page number to 1. */
{
jsInlineF(
"$(function() {\n"
" $('form').delegate('#%s','change keyup paste',function(e){\n"
" $('[name=cdwBrowseFiles_page]').val('1');\n"
" });\n"
"});\n"
, id);
}
-static void showTableFilterControlRow(struct fieldedTable *table, struct cart *cart,
- char *varPrefix, int maxLenField, struct hash *suggestHash)
+static void showTableFilterControlRow(struct fieldedTable *table, struct slName *visibleFields,
+ struct cart *cart, char *varPrefix, int maxLenField, struct hash *suggestHash)
/* Assuming we are in table already drow control row.
* The suggestHash is keyed by field name. If something is there we'll assume
* it's value is slName list of suggestion values */
{
/* Include javascript and style we need */
printf("\n");
printf("\n");
-int i;
printf("
");
/* Approximate size of input control in characters */
- int size = fieldedTableMaxColChars(table, i);
+ int size = fieldedTableMaxColChars(table, fieldIx);
if (size > maxLenField)
size = maxLenField;
/* Print input control getting previous value from cart. Set an id=
* so auto-suggest can find this control. */
char *oldVal = cartUsualString(cart, varName, "");
printf("\n");
else
printf(" value=\"%s\">\n", oldVal);
/* Write out javascript to reset page number to 1 if filter changes */
resetPageNumberOnChange(varName);
@@ -190,52 +192,52 @@
if (suggestHash != NULL)
{
struct slName *suggestList = hashFindVal(suggestHash, field);
if (suggestList != NULL)
{
printSuggestScript(varName, suggestList);
}
}
printf("
\n");
}
printf("
");
}
-static void showTableSortingLabelRow(struct fieldedTable *table, struct cart *cart, char *varPrefix,
- char *returnUrl)
+static void showTableSortingLabelRow(struct fieldedTable *table, struct slName *visibleFields,
+ struct cart *cart, char *varPrefix, char *returnUrl)
/* Put up the label row with sorting fields attached. ALso actually sort table. */
{
/* Get order var */
char orderVar[256];
safef(orderVar, sizeof(orderVar), "%s_order", varPrefix);
char *orderFields = cartUsualString(cart, orderVar, "");
char pageVar[64];
safef(pageVar, sizeof(pageVar), "%s_page", varPrefix);
/* Print column labels */
-int i;
-for (i=0; ifieldCount; ++i)
+struct slName *vis;
+for (vis = visibleFields; vis != NULL; vis = vis->next)
{
printf("
\n");
int fieldIx = 0;
- for (fieldIx=0; fieldIxfieldCount; ++fieldIx)
+ int i;
+ for (i=0; irow[fieldIx]);
char *val = longVal;
int valLen = strlen(val);
if (maxLenField > 0 && maxLenField < valLen)
{
if (valLen > maxLenField)
{
memcpy(shortVal, val, maxLenField-3);
shortVal[maxLenField-3] = 0;
strcat(shortVal, "...");
val = shortVal;
}
}
if (isNum[fieldIx]) // vacuous, but left it just in case we want to do different stuff to numbers later
@@ -376,318 +389,316 @@
// last page
printf(" ");
safef(id, sizeof id, "%s_last", varPrefix);
printf("⏭", id);
jsOnEventByIdF("click", id,
"$('[name=%s_page]').val('%d');\n"
"$('#submit').click();\n"
, varPrefix, totalPages);
}
}
}
}
void webFilteredFieldedTable(struct cart *cart, struct fieldedTable *table,
- char *returnUrl, char *varPrefix,
+ char *visibleFieldList, char *returnUrl, char *varPrefix,
int maxLenField, struct hash *tagOutputWrappers, void *wrapperContext,
- boolean withFilters, char *itemPlural,
- int pageSize, struct fieldedTableSegment *largerContext, struct hash *suggestHash,
+ boolean withFilters, char *pluralInstructions,
+ int pageSize, int facetUsualSize,
+ struct fieldedTableSegment *largerContext, struct hash *suggestHash,
struct facetField **ffArray, char *visibleFacetList,
void (*addFunc)(int) )
/* 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 */
+ * Pass in 0 for no max. */
{
if (strchr(returnUrl, '?') == NULL)
errAbort("Expecting returnUrl to include ? in showFieldedTable\nIt's %s", returnUrl);
-if (withFilters || visibleFacetList)
- showTableFilterInstructionsEtc(table, itemPlural, largerContext, addFunc, visibleFacetList);
+if (pluralInstructions != NULL)
+ showTableFilterInstructionsEtc(table, pluralInstructions, largerContext, addFunc, visibleFacetList);
if (visibleFacetList)
{
// Show top bar with quick-deselects for selected facet values
// as well a clear restriction button that cleans out cdwFile_filter cart var.
struct dyString *facetBar = dyStringNew(1024);
char *where = cartUsualString(cart, "cdwFile_filter", "");
boolean gotSelected = FALSE;
- struct slName *nameList = slNameListFromComma(visibleFacetList);
- int f;
- for (f = 0; f < table->fieldCount; ++f)
+ struct slName *visList = slNameListFromComma(visibleFacetList);
+ struct slName *vis;
+ for (vis = visList; vis != NULL; vis = vis->next)
{
+ int f = fieldedTableMustFindFieldIx(table, vis->name);
struct facetField *field = ffArray[f];
- if (slNameInListUseCase(nameList, field->fieldName)) // i.e. is this field a visible facet?
- {
if (!field->allSelected)
{
gotSelected = TRUE;
htmlDyStringPrintf(facetBar, "\n");
htmlDyStringPrintf(facetBar, "
\n");
htmlDyStringPrintf(facetBar, "
%s
\n", field->fieldName);
struct facetVal *val;
// Sort values alphabetically
// Make a copy to not disturb the original order
struct facetVal *valListCopy = facetsClone(field->valList);
slSort(&valListCopy, facetValCmp);
for (val = valListCopy; val; val=val->next)
{
boolean specificallySelected = (val->selected && !field->allSelected);
if (specificallySelected)
{
char *op = "remove";
htmlDyStringPrintf(facetBar, "
\n");
}
if (!isEmpty(where))
{
// left column
printf("Restricting files to where %s. ", where);
printf("  ");
printf("");
jsOnEventById("click", "clearRestrictionButton",
"$('[name=cdwBrowseFiles_page]').val('1');\n"
"$('[name=clearRestriction]').val('1');\n"
"$('#submit').click();\n");
printf(" ");
}
if (gotSelected)
{
// reset all facet value selections button
char *op = "resetAll";
- htmlPrintf("%s\n",
- cartSessionVarName(), cartSessionId(cart),
- op, "", "",
- "Clear All"
+ returnUrl, op, "", "", "Clear All"
);
printf("
\n");
printf("%s\n", facetBar->string);
printf("
\n");
}
if (!isEmpty(where) || gotSelected)
printf("
\n");
dyStringFree(&facetBar);
}
printf("
\n"); // parent container
if (visibleFacetList)
{
// left column
printf("
\n");
- struct slName *nameList = slNameListFromComma(visibleFacetList);
- int f;
- for (f = 0; f < table->fieldCount; ++f)
+ struct slName *visList = slNameListFromComma(visibleFacetList);
+ struct slName *vis;
+ for (vis = visList; vis != NULL; vis = vis->next)
{
+ int f = fieldedTableMustFindFieldIx(table, vis->name);
struct facetField *field = ffArray[f];
- if (slNameInListUseCase(nameList, field->fieldName)) // i.e. is this field a visible facet?
- {
htmlPrintf("
\n");
htmlPrintf("
%s
\n", field->fieldName);
struct facetVal *val;
if (!field->allSelected) // add reset facet link
{
char *op = "reset";
- htmlPrintf("
\n");
}
else if (val->selectCount > 0)
{
++valuesNotShown;
}
}
// show "See More" link when facet has lots of values
if (valuesNotShown > 0)
{
char *op = "showAllValues";
- htmlPrintf("
\n");
// Clicking a checkbox is actually a click on the following link
jsInlineF(
"$(function () {\n"
" $('.cdwFSCheckBox').click(function() {\n"
" this.nextSibling.nextSibling.click();\n"
" });\n"
"});\n");
}
// start right column, if there are two columns
if (visibleFacetList)
printf("
");
-
if (largerContext != NULL)
showTablePaging(table, cart, varPrefix, largerContext, pageSize);
+ }
+
if (visibleFacetList) // close right column, if there are two columns
printf("
");
printf("
\n"); //close parent container
}
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,
+webFilteredFieldedTable(cart, table, NULL, returnUrl, varPrefix,
maxLenField, tagOutputWrappers, wrapperContext,
FALSE, NULL,
- slCount(table->rowList), NULL, NULL, NULL, NULL, NULL);
+ slCount(table->rowList),
+ 0, 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))
@@ -759,83 +770,136 @@
safef(orderVar, sizeof(orderVar), "%s_order", varPrefix);
char *orderFields = cartUsualString(cart, orderVar, "");
if (!isEmpty(orderFields))
{
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, char *visibleFacetList,
- void (*addFunc)(int) )
-/* 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)
+struct dyString *fuseCsvFields(struct sqlConnection *conn, char *table,
+ char *firstCsv, char *secondCsv)
+/* Return a list that is firstCsv followed by any fields in secondCsv not already in firstCsv
+ * "a,b,c,d", "b,f,d,e" yeilds "a,b,c,d,f,e"
+ * order is preserved in firstCsv and when possible in second */
+{
+struct hash *uniq = hashNew(0);
+struct dyString *result = dyStringNew(0);
+
+/* Add everything in aList to both hash and result */
+struct slName *el;
+struct slName *aList = slNameListFromComma(firstCsv);
+for (el = aList; el != NULL; el = el->next)
+ {
+ if (sqlColumnExists(conn, table, el->name))
+ csvEscapeAndAppend(result, el->name);
+ hashAdd(uniq, el->name, NULL);
+ }
+
+/* Only add bList if it's not in there already and it is in database */
+struct slName *bList = slNameListFromComma(secondCsv);
+for (el = bList; el != NULL; el = el->next)
+ {
+ if (!hashLookup(uniq, el->name))
+ {
+ if (sqlColumnExists(conn, table, el->name))
+ csvEscapeAndAppend(result, el->name);
+ hashAdd(uniq, el->name, NULL);
+ }
+ }
+
+/* Clean up and return with result */
+slFreeList(&aList);
+slFreeList(&bList);
+hashFree(&uniq);
+return result;
+}
+
+void webFilteredSqlTable(struct cart *cart, /* User set preferences here */
+ struct sqlConnection *conn, /* Connection to database */
+ char *fields, char *from, char *initialWhere, /* Our query in three parts */
+ char *returnUrl, char *varPrefix, /* Url to get back to us, and cart var prefix */
+ int maxFieldWidth, /* How big do we let fields get in characters */
+ struct hash *tagOutWrappers, /* A hash full of callbacks, one for each column */
+ void *wrapperContext, /* Gets passed to callbacks in tagOutWrappers */
+ boolean withFilters, /* If TRUE put up filter controls under labels */
+ char *pluralInstructions, /* If non-NULL put up instructions and clear/search buttons */
+ int pageSize, /* How many items per page */
+ int facetUsualSize, /* How many items in a facet before opening */
+ struct hash *suggestHash, /* If using filter can put suggestions for categorical items here */
+ char *visibleFacetList, /* Comma separated list of fields to facet on */
+ void (*addFunc)(int) ) /* Callback relevant with pluralInstructions only */
+/* Turn sql query into a nice interactive table, possibly with facets. It constructs
+ * 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. Optionally table
+ * may have a faceted search to the left or fields that can filter under the labels. The 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);
+struct dyString *fusedFields = fuseCsvFields(conn, from, fields, visibleFacetList);
+webTableBuildQuery(cart, from, initialWhere, varPrefix,
+ fusedFields->string, withFilters, &query, &where);
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;
page = cartUsualInt(cart, pageVar, 0) - 1;
if (page < 0)
page = 0;
context.tableOffset = page * pageSize;
if (visibleFacetList)
{
- table = fieldedTableAndCountsFromDbQuery(conn, query->string, pageSize, context.tableOffset, selectedFacetValues, &ffArray, &context.tableSize);
+ 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);
}
-webFilteredFieldedTable(cart, table, returnUrl, varPrefix, maxFieldWidth,
- tagOutWrappers, wrapperContext, withFilters, itemPlural, pageSize, &context, suggestHash, ffArray, visibleFacetList, addFunc);
+webFilteredFieldedTable(cart, table, fields, returnUrl, varPrefix, maxFieldWidth,
+ tagOutWrappers, wrapperContext, withFilters, pluralInstructions,
+ pageSize, facetUsualSize, &context, suggestHash, ffArray, visibleFacetList, addFunc);
fieldedTableFree(&table);
+dyStringFree(&fusedFields);
dyStringFree(&query);
dyStringFree(&where);
}