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("&nbsp&nbsp;");
 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&nbsp;%s&nbsp;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("&GT;min or &LT;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);
 }