From 34f17775ad3fa4e61d792d52675b5756719a3fda Mon Sep 17 00:00:00 2001 From: arlen Date: Wed, 22 Sep 2021 14:09:33 -0400 Subject: [PATCH] Matchgrid Records: implement filtering/search on index view (CO-1871) --- app/src/Locale/en_US/default.po | 21 +++ app/src/Template/Element/javascript.ctp | 44 +++++ app/src/Template/Element/search.ctp | 178 ++++++++++++++++++ app/src/Template/MatchgridRecords/columns.inc | 28 ++- app/src/Template/Standard/index.ctp | 16 +- app/webroot/css/co-base.css | 129 +++++++++++++ app/webroot/css/co-responsive.css | 11 ++ app/webroot/js/comanage.js | 10 + 8 files changed, 431 insertions(+), 6 deletions(-) create mode 100644 app/src/Template/Element/search.ctp diff --git a/app/src/Locale/en_US/default.po b/app/src/Locale/en_US/default.po index a081e4160..c2fe138af 100644 --- a/app/src/Locale/en_US/default.po +++ b/app/src/Locale/en_US/default.po @@ -324,6 +324,9 @@ msgstr "Requested Reference ID must already be in use, or be the keyword 'new'" msgid "match.fd.action" msgstr "Action" +msgid "match.fd.all" +msgstr "All" + msgid "match.fd.alphanumeric" msgstr "Alphanumeric" @@ -462,9 +465,15 @@ msgstr "(Please select a value)" msgid "match.fd.sor" msgstr "System of Record" +msgid "match.fd.sor.abbr" +msgstr "SOR" + msgid "match.fd.sorid" msgstr "System of Record ID" +msgid "match.fd.sorid.abbr" +msgstr "SOR ID" + msgid "match.fd.status" msgstr "Status" @@ -502,6 +511,9 @@ msgstr "There are no matchgrids currently defined." msgid "match.in.pagination.format" msgstr "Page {{page}} of {{pages}}, Viewing {{start}}-{{end}} of {{count}}" +msgid "match.in.records.none" +msgstr "No records to display" + ### Operations (Commands) msgid "match.op.add.a" msgstr "Add New {0}" @@ -518,6 +530,12 @@ msgstr "Build" msgid "match.op.build.confirm" msgstr "Are you sure you wish to (re)build this matchgrid?" +msgid "match.op.clear" +msgstr "Clear" + +msgid "match.op.clear.filters" +msgstr "{0,plural,=1{Clear Filter} other{Clear Filters}}" + msgid "match.op.configure.a" msgstr "Configure {0}" @@ -545,6 +563,9 @@ msgstr "Edit {0}" msgid "match.op.first" msgstr "First" +msgid "match.op.filter" +msgstr "Filter" + msgid "match.op.go" msgstr "Go" diff --git a/app/src/Template/Element/javascript.ctp b/app/src/Template/Element/javascript.ctp index ab33a6fb6..f02d7f712 100644 --- a/app/src/Template/Element/javascript.ctp +++ b/app/src/Template/Element/javascript.ctp @@ -118,6 +118,50 @@ $("#user-panel").hide(); } }); + + // TOP SEARCH FILTER FORM + // Send only non-empty fields in the form + $("#top-search-form").submit(function() { + $("#top-search-form *").filter(':input').each(function () { + if($(this).val() == '') { + $(this).prop('disabled',true); + } + }); + }); + + // Toggle the top search filter box + $("#top-search-toggle, #top-search-toggle button.cm-toggle").click(function(e) { + e.preventDefault(); + e.stopPropagation(); + if ($("#top-search-fields").is(":visible")) { + $("#top-search-fields").hide(); + $("#top-search-toggle button.cm-toggle").attr("aria-expanded","false"); + $("#top-search-toggle button.cm-toggle .drop-arrow").text("arrow_drop_down"); + } else { + $("#top-search-fields").show(); + $("#top-search-toggle button.cm-toggle").attr("aria-expanded","true"); + $("#top-search-toggle button.cm-toggle .drop-arrow").text("arrow_drop_up"); + } + }); + + // Clear a specific top search filter by clicking the filter button + $("#top-search-toggle button.top-search-active-filter").click(function(e) { + e.preventDefault(); + e.stopPropagation(); + $(this).hide(); + filterId = '#' + $(this).attr("aria-controls"); + $(filterId).val(""); + $(this).closest('form').submit(); + }); + + // Clear all top filters from the filter bar + $("#top-search-clear-all-button").click(function(e) { + e.preventDefault(); + e.stopPropagation(); + $(this).hide(); + $("#top-search-toggle .top-search-active-filter").hide(); + $("#top-search-clear").click(); + }); // Accordion $(".accordion").accordion(); diff --git a/app/src/Template/Element/search.ctp b/app/src/Template/Element/search.ctp new file mode 100644 index 000000000..3fcfc0666 --- /dev/null +++ b/app/src/Template/Element/search.ctp @@ -0,0 +1,178 @@ +request->getParam('controller'); +$req = Inflector::singularize($controller); + +// Get the query string and separate the search params from the non-search params +$query = $this->request->getQueryParams(); +$non_search_params = array_diff_key($query, $vv_search_fields); +$search_params = array_intersect_key($query, $vv_search_fields); + +// Begin the form +print $this->Form->create($req, [ + 'id' => 'top-search-form', + 'type' => 'get', + 'url' => ['action' => $this->request->action] +]); + +// Pass back the non-search params as hidden fields, but always exclude the page parameter +// because we need to start new searches on page one (or we're likely to end up with a 404). +if(!empty($non_search_params)) { + foreach ($non_search_params as $param => $value) { + if($param != 'page') { + print $this->Form->hidden(filter_var($param, FILTER_SANITIZE_SPECIAL_CHARS), array('default' => filter_var($value, FILTER_SANITIZE_SPECIAL_CHARS))) . "\n"; + } + } +} + +// Boolean to distinguish between search filters and sort parameters +// XXX may no longer need this +$hasActiveFilters = false; +?> + + + +Form->end();?> \ No newline at end of file diff --git a/app/src/Template/MatchgridRecords/columns.inc b/app/src/Template/MatchgridRecords/columns.inc index 3c7b42253..1a1f5f4ee 100644 --- a/app/src/Template/MatchgridRecords/columns.inc +++ b/app/src/Template/MatchgridRecords/columns.inc @@ -28,21 +28,38 @@ // We need to add matchgrid ID to all links (since ID is unique only within a matchgrid) // eg: matchgrid-records/edit/48?matchgrid_id=3 $forcePrimaryLink = true; + +// Turn on the search/filter box for this index view +// If $enableSearch is true, search input labels will be 'label' unless overridden by 'searchLabel'. +// If $enableSearch is true, a specific field can be excluded from the search by adding 'searchSkip' => true. +$enableSearch = true; // Note we don't need to do any special filtering, since MatchgridRecordsController will // force the model's table only to the one associated with the matchgrid $indexColumns = [ 'id' => [ - 'type' => 'echo' + 'label' => __('match.fd.id'), + 'type' => 'echo', + 'searchSkip' => true ], 'sor' => [ - 'type' => 'echo' + 'label' => __('match.fd.sor.abbr'), + 'type' => 'echo', + 'searchType' => 'select', + 'searchLabel' => __('match.fd.sor'), + 'searchEmpty' => __('match.fd.all'), + 'searchOptions' => $sor ], 'sorid' => [ - 'type' => 'echo' + 'label' => __('match.fd.sorid.abbr'), + 'type' => 'echo', + 'searchType' => 'text', + 'searchLabel' => __('match.fd.sorid') ], 'referenceid' => [ - 'type' => 'echo' + 'label' => __('match.fd.referenceid'), + 'type' => 'echo', + 'searchType' => 'text' ] ]; @@ -51,7 +68,8 @@ foreach($attributes as $attr) { if($attr->index_display === true) { $indexColumns[$attr->name] = [ 'label' => $attr->name, - 'type' => 'echo' + 'type' => 'echo', + 'searchType' => 'text' ]; } } \ No newline at end of file diff --git a/app/src/Template/Standard/index.ctp b/app/src/Template/Standard/index.ctp index 64e5f20a7..dc9633845 100644 --- a/app/src/Template/Standard/index.ctp +++ b/app/src/Template/Standard/index.ctp @@ -42,6 +42,10 @@ $modelsName = $this->name; // $tablefk = model_id $tableFK = Inflector::singularize($vv_tablename) . "_id"; +// Do we have records for this index? This will be set to true during render if we do. +// Otherwise, we'll print out a "no records" message. +$recordsExist = false; + // Our default link actions, in order of preference, unless the column config overrides it $linkActions = ['edit', 'view']; @@ -132,7 +136,13 @@ function _column_key($modelsName, $c, $tz=null) { - + + + + element('search', ['vv_search_fields' => $indexColumns]); ?> + + +
@@ -316,7 +326,11 @@ function _column_key($modelsName, $c, $tz=null) { ?> + + + +
diff --git a/app/webroot/css/co-base.css b/app/webroot/css/co-base.css index fe0576df7..17f100cac 100644 --- a/app/webroot/css/co-base.css +++ b/app/webroot/css/co-base.css @@ -507,6 +507,135 @@ body.logged-in #top-menu { clear: both; margin-top: 1em; } +/* TOP SEARCH */ +.top-search { + margin-top: 0.5em; + padding: 0; +} +.top-search legend .material-icons { + vertical-align: middle; +} +.top-search legend button.cm-toggle { + position: absolute; + right: 0.5em; + border: none; + background-color: transparent; +} +#content .top-search legend .material-icons { + font-size: 20px; +} +#content .top-search legend button.cm-toggle .material-icons { + font-size: 34px; + line-height: 17px; +} +.top-search fieldset { + clear: both; + position: relative; + padding: 0.5em 0.5em 0; + margin: 0; + background-color: #f5f5f5; +} +.top-search.open fieldset { + padding-bottom: 0.5em; +} +.top-search legend { + width: 100%; + background-color: #f5f5f5; + margin: 0 -0.5em; + padding: 0.5em 0.5em 0; + line-height: 1.8em; + cursor: pointer; + font-size: 1em; + box-sizing: content-box; +} +#top-search-fields { + display: none; + padding: 0.25em 0.5em; +} +#top-search-submit { + float: right; + width: 200px; + margin-bottom: 0.75em; +} +.top-search input[type=text], +.top-search select, +.side-search input[type=text], +.side-search select { + width: 100%; + box-sizing: border-box; + margin: 0 0 0.5em 0; + height: 28px; + padding: 2px 4px; + border: 1px solid #ddd; + background-color: #fff; +} +.top-search label { + margin-bottom: 0; +} +::-webkit-input-placeholder, +::-moz-placeholder, +:-ms-input-placeholder, +:-moz-placeholder { + opacity: 0.2; +} +.top-search input[type=text]:focus, +.side-search input[type=text]:focus { + background-color: #ffd; +} +.top-search .submit-button, +.top-search .clear-button, +.side-search .submit-button, +.side-search .clear-button { + float: right; + font-size: 0.9em; + width: 80px; + line-height: 28px; + height: 28px; + margin: 1em 0.5em; + padding: 0; +} +.top-search .top-search-checkboxes { + margin-top: 0.5em; +} +.top-search .filter-clear-all-button { + font-size: 0.9em; + width: auto; + height: auto; + line-height: unset; + margin: 0; + padding: 0 1em; +} +.top-search .filter-clear-all-button:hover { + background-color: unset; +} +.top-search.top-search-hide-fields label, +.side-search label { + display: none; +} +#top-search-active-filters { + margin-left: 1em; + padding: 0.5em 0; +} +.top-search-active-filter { + margin: 0 0.25em 0 0; + white-space: nowrap; + /*color: #b00;*/ + font-style: italic; +} +.top-search-active-filter-title::after { + content: ": "; +} +.top-search-active-filter-title.no-value::after { + content: none; +} +.top-search-active-filters-remove button { + margin-left: 2em; + font-size: 0.9em; + background-color: #f5f5f5; +} +.top-search-active-filters-remove button:hover { + background-color: #eee; +} /* RECONCILE TABLE */ #reconcile-table { width: auto; diff --git a/app/webroot/css/co-responsive.css b/app/webroot/css/co-responsive.css index 03cfd9234..dfadf3ad3 100644 --- a/app/webroot/css/co-responsive.css +++ b/app/webroot/css/co-responsive.css @@ -191,6 +191,17 @@ .call-to-action { margin-bottom: 0; } + /* TOP SEARCH */ + #top-search-fields-subgroups { + display: grid; + grid-template-columns: 1fr 1fr; + column-gap: 1em; + padding: 0 0.5em; + } + #top-search-submit.tss-rebalance { + margin-top: -3.5em; + position: relative; + } /* MATCHGRID MANAGEMENT */ #matchgrid-config-menu { display: grid; diff --git a/app/webroot/js/comanage.js b/app/webroot/js/comanage.js index 34a556262..213c7fd1e 100644 --- a/app/webroot/js/comanage.js +++ b/app/webroot/js/comanage.js @@ -226,3 +226,13 @@ function limitPage(pageLimit,recordCount,currentPage) { window.location = currentUrl; } +// Clear the top search form for index views +// formObj - form object (DOM form obj, required) +function clearTopSearch(formObj) { + for (var i=0; i