Skip to content

Commit

Permalink
Improve global search bar and search results page (CFM-220) (#59)
Browse files Browse the repository at this point in the history
* Improve global search bar and search results page; fix double-encoding of flash messages. (CFM-220)

* Improve global search result count messages (CFM-220)
  • Loading branch information
arlen authored Dec 6, 2022
1 parent cba9a07 commit 1f8a4d9
Show file tree
Hide file tree
Showing 14 changed files with 422 additions and 191 deletions.
3 changes: 3 additions & 0 deletions app/resources/locales/en_US/operation.po
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ msgstr "Save"
msgid "search"
msgstr "Search"

msgid "search.global"
msgstr "Global Search"

msgid "skip_to_content"
msgstr "Skip to main content"

Expand Down
15 changes: 12 additions & 3 deletions app/resources/locales/en_US/result.po
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,20 @@ msgstr "Search limit reached"
msgid "search.none"
msgstr "No results found"

msgid "search.result.found"
msgstr "Found"

msgid "search.result.found.modelCount"
msgstr "{0} {1}"

msgid "search.result.id"
msgstr "({0})"
msgstr "ID {0}"

msgid "search.result.related"
msgstr "({0}, {1}: {2})"
msgstr "{0}: {1}, ID {2}"

msgid "search.results"
msgstr "Search Results"
msgstr "Search Results"

msgid "search.retry"
msgstr "Please select an option from a menu, or try your search again."
19 changes: 14 additions & 5 deletions app/src/Controller/DashboardsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,21 @@ public function search() {

// XXX Still need to implement this (see also CFM-126)
$roles = [];

if(!empty($this->request->getData('q'))
// Only process the request if there are non-space characters
&& !ctype_space($this->request->getData('q'))) {
// Trim leading and trailing whitespace

// Gather our search string.
$q = '';
if(!empty($this->request->getData('global-search-q'))) {
// A search was passed in from the global search-bar.
$q = trim($this->request->getData('global-search-q'));
// Now pass the search string to the in-page search form and empty the global search bar.
$this->setRequest($this->getRequest()->withData('global-search-q', '')->withData('q', $q));
} elseif(!empty($this->request->getData('q'))) {
// A search was passed in from the form on the Global Search page.
$q = trim($this->request->getData('q'));
}

// Only process the request if we have a string of non-space characters
if(!empty($q)) {

// Pull our search configuration
$CoSettings = TableRegistry::getTableLocator()->get('CoSettings');
Expand Down
201 changes: 166 additions & 35 deletions app/templates/Dashboards/search.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,47 +25,178 @@
* @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
*/

// Start with the Primary Registry objects
foreach(['People', 'Groups'] as $pm) {
if(!empty($vv_results[$pm])) {
print "<h2>" . __d('controller', $pm, 2) . "</h2>\n";
print "<ul>\n";
$options = [
'type' => 'post',
'url' => [
'plugin' => null,
'controller' => 'dashboards',
'action' => 'search'
],
'id' => 'search'
];

foreach($vv_results[$pm] as $pkey => $matches) {
$url = [
'controller' => \Cake\Utility\Inflector::dasherize($pm),
'action' => 'edit',
$pkey
];
$noResults = true;
?>

// The same entity can match on more than one searchable model
<div class="pageTitleContainer">
<div class="pageTitle">
<h1><?= __d('operation','search.global'); ?></h1>
</div>
</div>

foreach($matches as $m => $entity) {
$displayField = $vv_supported_models[$m]['displayField'];
$displayLabel = __d('field', $displayField);
$displayString = $entity->$displayField;
<div id="search-container" aria-labelledby="global-search-toggle">
<?php
print $this->Form->create(null, $options);
print $this->Form->hidden('co_id', ['default' => $vv_cur_co->id]);
?>
<div class="input-group">
<?php
print $this->Form->label(
'q',
__d('operation','search'),
[
'class' => 'visually-hidden'
]
);
print $this->Form->input(
'q',
[
'id' => 'q',
'class' => 'form-control',
'placeholder' => __d('field','search.placeholder')
]
);
print $this->Form->button(
'<span class="material-icons-outlined">close</span>',
['type' => 'button', 'escapeTitle' => false, 'id' => 'search-clear', 'class' => 'btn btn-link']
);
print $this->Form->button(
__d('operation','search'),
['type' => 'submit', 'escapeTitle' => false, 'class' => 'btn btn-primary btn-sm']
);
?>
</div>
<?php
print $this->Form->end();
?>

// If we match on a related model (for example PersonRoles for People)
// indicate what actually matched
$matchInfo = __d('result', 'search.result.id', $pkey);
<?php /* keep the following temporarily:
<div id="global-search-type">
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" id="gs-type-basic" name="gs-type">
<label class="form-check-label" for="gs-type-basic">
basic
</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" id="gs-type-advanced" name="gs-type">
<label class="form-check-label" for="gs-type-advanced">
advanced
</label>
</div>
</div> */ ?>
</div>

<h2><?= $vv_title; ?></h2>

// Do we have a more informative string to render?
if(!empty($entity->person->primary_name->full_name)) {
$displayString = $entity->person->primary_name->full_name;
<!-- Flash Messages and defined Info Banners -->
<div class="alert-container" id="flash-messages">
<?= $this->Flash->render() ?>

$matchInfo = __d('result', 'search.result.related', $pkey, $displayLabel, $entity->$displayField);
} elseif(!empty($entity->group->name)) {
$displayString = $entity->group->name;
<?php if(!empty($indexBanners)): ?>
<?php foreach($indexBanners as $b): ?>
<?= $this->Alert->alert($b, 'warning') ?>
<?php endforeach; // $indexBanners ?>
<?php endif; // $indexBanners ?>

$matchInfo = __d('result', 'search.result.related', $pkey, $displayLabel, $displayString);
}
<?php if(!empty($banners)): ?>
<?php foreach($banners as $b): ?>
<?= $this->Alert->alert($b, 'warning') ?>
<?php endforeach; // $banners ?>
<?php endif; // $banners ?>
</div>

// XXX This construction isn't ideal, but presumably will get rewritten when the
// design of this page is redone
print "<li>" . $this->Html->link($displayString, $url) . " " . filter_var($matchInfo, FILTER_SANITIZE_SPECIAL_CHARS). "</li>\n";
}
}
<?php
$peopleResultsCount = count($vv_results['People']);
$groupsResultsCount = count($vv_results['Groups']);
?>
<?php if($peopleResultsCount || $groupsResultsCount): ?>
<?php $noResults = false; ?>
<ul id="search-results-meta">
<li class="search-results-found"><?= __d('result', 'search.result.found') ?></li>
<?php if($peopleResultsCount): ?>
<li>
<?= __d('result','search.result.found.modelCount', [$peopleResultsCount, __d('controller', "People", [$peopleResultsCount])]); ?>
</li>
<?php endif; ?>
<?php if($groupsResultsCount): ?>
<li>
<?= __d('result','search.result.found.modelCount', [$groupsResultsCount, __d('controller', "Groups", [$groupsResultsCount])]); ?>
</li>
<?php endif; ?>
</ul>
<?php endif; ?>

<div id="search-results">
<!-- Start with the Primary Registry objects -->
<?php foreach(['People', 'Groups'] as $pm): ?>
<?php if(!empty($vv_results[$pm])): ?>
<div class="search-results-group-container">
<h3><?= __d('controller', $pm, 2); ?></h3>
<ul class="search-results-group">

<?php foreach($vv_results[$pm] as $pkey => $matches): ?>
<?php
$url = [
'controller' => \Cake\Utility\Inflector::dasherize($pm),
'action' => 'edit',
$pkey
];
// The same entity can match on more than one searchable model
?>

<?php foreach($matches as $m => $entity): ?>
<?php
$displayField = $vv_supported_models[$m]['displayField'];
$displayLabel = __d('field', $displayField);
$displayString = $entity->$displayField;

// If we match on a related model (for example PersonRoles for People)
// indicate what actually matched
$matchInfo = __d('result', 'search.result.id', $pkey);

// Do we have a more informative string to render?
if(!empty($entity->person->primary_name->full_name)) {
$displayString = $entity->person->primary_name->full_name;

$matchInfo = __d('result', 'search.result.related', $displayLabel, $entity->$displayField, $pkey);
} elseif(!empty($entity->group->name)) {
$displayString = $entity->group->name;

$matchInfo = __d('result', 'search.result.related', $displayLabel, $displayString, $pkey);
}
?>
<li class="search-result">
<a href="<?= $this->Url->build($url) ?>">
<div class="search-result-name">
<?= $displayString ?>
</div>
<div class="search-result-match-info">
<?= filter_var($matchInfo, FILTER_SANITIZE_SPECIAL_CHARS) ?>
</div>
</a>
</li>
<?php endforeach; ?>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<?php endforeach; ?>

print "</ul>\n";
}
}
<?php if($noResults): ?>
<p>
<?= __d('result','search.retry'); ?>
</p>
<?php endif; ?>

</div>
2 changes: 1 addition & 1 deletion app/templates/element/flash/default.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
?>

<?php if(!empty($message)): ?>
<?= $this->Alert->alert(h($message), 'warning', true, __d('information','flash.default')) ?>
<?= $this->Alert->alert($message, 'warning', true, __d('information','flash.default')) ?>
<?php endif; ?>

2 changes: 1 addition & 1 deletion app/templates/element/flash/error.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
*/ ?>

<?php if(!empty($message)): ?>
<?= $this->Alert->alert(h($message), 'danger', true, __d('information','flash.error')) ?>
<?= $this->Alert->alert($message, 'danger', true, __d('information','flash.error')) ?>
<?php endif; ?>
4 changes: 3 additions & 1 deletion app/templates/element/flash/information.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
?>

<?php if(!empty($message)): ?>
<?= $this->Alert->alert(h($message), 'information', true, __d('information','flash.information')) ?>
<?php /* Note: unlike Notice, Error, and Success messages, Information messages require
no prefix. That is, we don't include "Information: " in front of the message. */ ?>
<?= $this->Alert->alert($message, 'information', true) ?>
<?php endif; ?>
2 changes: 1 addition & 1 deletion app/templates/element/flash/success.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
?>

<?php if(!empty($message)): ?>
<?= $this->Alert->alert(h($message), 'success', true, __d('information','flash.success')) ?>
<?= $this->Alert->alert($message, 'success', true, __d('information','flash.success')) ?>
<?php endif; ?>
29 changes: 26 additions & 3 deletions app/templates/element/javascript.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,32 @@

// END DESKTOP MENU DRAWER BEHAVIOR

// GLOBAL SEARCH
$('#search-bar input').focus(function() {
$('#search-bar button').addClass('visible');
// SEARCH
// Persistent search bar form:
$('#global-search form').submit(function () {
// Disallow submit on blank
if($.trim($('#global-search-q').val()) == '') {
return false;
}
});
// Select search text on focus
$('#global-search-q').focus(function() {
$('#global-search-q').select();
});

// Search page form:
$('#search').submit(function () {
// Disallow submit on blank
if($.trim($('#q').val()) == '') {
return false;
}
});
$('#search-clear').click(function () {
$('#q').val('');
$('#q').focus();
});
$('#q').focus(function() {
$('#q').select();
});

// TOP FILTER FORM
Expand Down
33 changes: 23 additions & 10 deletions app/templates/element/searchGlobal.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,29 @@
'plugin' => null,
'controller' => 'dashboards',
'action' => 'search'
]
],
'id' => 'global-search-form'
];

print $this->Form->create(null, $options);
print $this->Form->hidden('co_id', ['default' => $vv_cur_co->id]);
print $this->Form->label('q', __d('field','search.placeholder'), ['class' => 'visually-hidden']);
print $this->Form->input('q',['id' => 'q','placeholder' => __d('field','search.placeholder')]);
print $this->Form->button(
__d('operation','search'),
['type' => 'submit', 'escapeTitle' => false, 'class' => 'btn btn-primary']
);
print $this->Form->end();
?>

<button id="global-search-toggle" class="dropdown-toggle top-menu-button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<em class="material-icons">search</em>
<span class="visually-hidden"><?= __d('operation','search') ?></span>
</button>

<div id="global-search" class="dropdown-menu" aria-labelledby="global-search-toggle">
<?php
print $this->Form->create(null, $options);
print $this->Form->hidden('co_id', ['default' => $vv_cur_co->id]);
print $this->Form->label('global-search-q', __d('field','search.placeholder'), ['class' => 'visually-hidden']);
print $this->Form->input('global-search-q',['id' => 'global-search-q', 'placeholder' => __d('field','search.placeholder')]);
print $this->Form->button(
'<em class="material-icons">search</em>',
['type' => 'submit', 'escapeTitle' => false, 'id' => 'global-search-button', 'class' => 'btn btn-link btn-sm']
);
print $this->Form->end();
?>
</div>


Loading

0 comments on commit 1f8a4d9

Please sign in to comment.