Skip to content

Commit

Permalink
[WIP]Support date range filtering #2
Browse files Browse the repository at this point in the history
  • Loading branch information
Ioannis Igoumenos committed May 8, 2022
1 parent 13a559e commit 33c9bca
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 28 deletions.
38 changes: 32 additions & 6 deletions app/src/Lib/Traits/SearchFilterTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ public function getSearchableAttributes(): array {
'type' => $type,
'label' => (__d('field', $column) ?? Inflector::humanize($column))
];

// For the date fields we search ranges
if($type === 'timestamp') {
$this->searchFilters[$column]['alias'][] = $column . '_starts_at';
$this->searchFilters[$column]['alias'][] = $column . '_ends_at';
}
}

return $this->searchFilters ?? [];
Expand All @@ -60,14 +66,14 @@ public function getSearchableAttributes(): array {
* Build a query where() clause for the configured attribute.
*
* @param \Cake\ORM\Query $query Cake ORM Query object
* @param string|array $attribute Attribute to filter on (database name)
* @param string $q Value to filter on
* @param string $attribute Attribute to filter on (database name)
* @param string|array $q Value to filter on
*
* @return \Cake\ORM\Query Cake ORM Query object
* @since COmanage Registry v5.0.0
*/

public function whereFilter(\Cake\ORM\Query $query, string|array $attribute, string $q): object {
public function whereFilter(\Cake\ORM\Query $query, string $attribute, string|array $q): object {
// not a permitted attribute
if(empty($this->searchFilters[$attribute])) {
return $query;
Expand All @@ -83,9 +89,29 @@ public function whereFilter(\Cake\ORM\Query $query, string|array $attribute, str
} elseif(in_array($this->searchFilters[$attribute]['type'], $search_types, true)) {
return $query->where([$attribute => $search]);
} elseif( $this->searchFilters[$attribute]['type'] == "timestamp") {
return $query->where(function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) use ($attribute, $search) {
return $exp->between($attribute, $search[0], $search[1]);
});
// Date between dates
if(!empty($search[0])
&& !empty($search[1])) {
return $query->where(function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) use ($attribute, $search) {
return $exp->between($attribute, $search[0], $search[1]);
});
// The starts at is non empty. So the data should be greater than the starts_at date
} elseif(!empty($search[0])
&& empty($search[1])) {
return $query->where(function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) use ($attribute, $search) {
return $exp->gte($attribute, $search[0]);
});
// The ends at is non-empty. So the data should be less than the ends at date
} elseif(!empty($search[1])
&& empty($search[0])) {
return $query->where(function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) use ($attribute, $search) {
return $exp->lte($attribute, $search[1]);
});
} else {
// We return everything
return $query;
}

}

// String values
Expand Down
2 changes: 1 addition & 1 deletion app/templates/Standard/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ function _column_key($modelsName, $c, $tz=null) {
break;
case 'datetime':
// XXX dates can be rendered as eg $entity->created->format(DATE_RFC850);
print $this->Time->nice($entity->$col, $vv_tz) . $suffix;
print !empty($entity->$col) ? $this->Time->nice($entity->$col, $vv_tz) . $suffix : "";
break;
case 'enum':
if($entity->$col) {
Expand Down
78 changes: 58 additions & 20 deletions app/templates/element/filter.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,48 @@
* @since COmanage Registry v5.0.0
* @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
*/
use Cake\Collection\Collection;
use Cake\Utility\Inflector;


// $this->name = Models
$modelsName = $this->name;
// $modelName = Model
$modelName = \Cake\Utility\Inflector::singularize($modelsName);
$modelName = Inflector::singularize($modelsName);

// 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_searchable_attributes);
$search_params = array_intersect_key($query, $vv_searchable_attributes);
// Search attributes collection
$search_attributes_collection = new Collection($vv_searchable_attributes);
$alias_params = $search_attributes_collection->filter(fn ($val, $attr) => (is_array($val) && array_key_exists('alias', $val)) )
->extract('alias')
->unfold()
->toArray();
// For the non search params we need to search the alias params as well
$searchable_parameters = [
...array_keys($vv_searchable_attributes),
...$alias_params
];
$non_search_params = (new Collection($query))->filter( fn($value, $key) => !in_array($key, $searchable_parameters) )
->toArray();

// Filter the search params and take params with aliases into consideration
$search_params = [];
foreach ($vv_searchable_attributes as $attr => $value) {
if(isset($query[$attr])) {
$search_params[$attr] = $query[$attr];
continue;
}

if(isset($value['alias'])
&& is_array($value['alias'])) {
foreach ($value['alias'] as $alias_key) {
if(isset($query[$alias_key])) {
$search_params[$attr][$alias_key] = $query[$alias_key];
}
}
}
}

// Begin the form
print $this->Form->create(null, [
Expand Down Expand Up @@ -61,7 +93,6 @@
<em class="material-icons">search</em>
<?= __d('operation', 'filter'); ?>


<?php if(!empty($search_params)):?>
<span id="top-filters-active-filters">
<?php foreach($search_params as $key => $params): ?>
Expand All @@ -74,16 +105,18 @@

// The populated variables are in plural while the column names are singular
// Convention: It is a prerequisite that the vvar should be the plural of the column name
$populated_vvar = Cake\Utility\Inflector::pluralize($key);
$pvalue = isset($$populated_vvar) ? $$populated_vvar[ $search_params[$key] ] : $search_params[$key];
$populated_vvar = Inflector::pluralize($key);
$button_label = isset($$populated_vvar) ?
$$populated_vvar[ $search_params[$key] ] :
(is_array($search_params[$key]) ? 'Range' : $search_params[$key]);
?>
<button class="top-filters-active-filter deletebutton spin btn btn-default btn-sm" type="button" aria-controls="<?php print $aria_controls; ?>" title="<?= __d('operation', 'clear.filters',[2]); ?>">
<em class="material-icons">cancel</em>
<span class="top-filters-active-filter-title">
<?= $vv_searchable_attributes[$key]['label']; ?>
<?= $vv_searchable_attributes[$key]['label'] ?>
</span>
<span class="top-filters-active-filter-value">
<?= filter_var($pvalue, FILTER_SANITIZE_SPECIAL_CHARS); ?>
<?= filter_var($button_label, FILTER_SANITIZE_SPECIAL_CHARS); ?>
</span>
</button>
<?php endforeach; ?>
Expand All @@ -97,7 +130,7 @@
<button class="cm-toggle nospin" aria-expanded="false" aria-controls="top-filters-fields" type="button"><em class="material-icons drop-arrow">arrow_drop_down</em></button>
</legend>
<div id="top-filters-fields">
<div id="top-filters-fields-subgroups">
<div class="top-filters-fields-subgroups">
<?php
$field_booleans_columns = [];
$field_datetime_columns = [];
Expand All @@ -120,7 +153,7 @@

// The populated variables are in plural while the column names are singular
// Convention: It is a prerequisite that the vvar should be the plural of the column name
$populated_vvar = Cake\Utility\Inflector::pluralize($key);
$populated_vvar = Inflector::pluralize($key);
if(isset($$populated_vvar)) {
// If we have an AutoViewVar matching the name of this key,
// convert to a select
Expand All @@ -134,11 +167,11 @@
}
?>
</div>
<?php if(!empty($field_booleans_columns)): ?>
<?php if(!empty($field_datetime_columns)): ?>
<?php foreach($field_datetime_columns as $key => $options): ?>
<div class="input">
<div class="top-search-date-label"><?= Cake\Utility\Inflector::humanize($key) ?></div>
<div id="top-filters-fields-subgroups">
<div class="top-search-date-label"><?= Inflector::humanize($key) ?></div>
<div class="top-filters-fields-subgroups">
<!-- Start at -->
<div class="top-search-date-fields">
<div class="d-flex">
Expand All @@ -149,14 +182,16 @@
$starts_field = $key . "_starts_at";
$coptions['class'] = 'form-control datepicker';
$coptions['label'] = 'Starts at:';
$coptions['placeholder'] = 'YYYY-MM-DD HH:MM:SS'; // TODO: test for date-only inputs and send only the date
$coptions['required'] = false;
$coptions['placeholder'] = 'YYYY-MM-DD HH:MM:SS';
$coptions['id'] = $starts_field;

$pickerDate = '';
if(!empty($query[$starts_field])) {
$starts_date = \Cake\I18n\FrozenTime::parse($query[$starts_field]);
// Adjust the time back to the user's timezone
$coptions['value'] = $query[$starts_field]->i18nFormat("yyyy-MM-dd HH:mm:ss", $this->get('vv_tz'));
$pickerDate = $query[$starts_field]->i18nFormat("yyyy-MM-dd", $this->get('vv_tz'));
$coptions['value'] = $starts_date->i18nFormat("yyyy-MM-dd HH:mm:ss", $this->get('vv_tz'));
$pickerDate = $starts_date->i18nFormat("yyyy-MM-dd", $this->get('vv_tz'));
}

$date_args = [
Expand All @@ -178,15 +213,17 @@
// accessibility purposes.
$ends_field = $key . "_ends_at";
$coptions['class'] = 'form-control datepicker';
$coptions['label'] = 'Ends at:';
$coptions['required'] = false;
$coptions['placeholder'] = 'YYYY-MM-DD HH:MM:SS'; // TODO: test for date-only inputs and send only the date
$coptions['label'] = 'Ends at:';
$coptions['id'] = $ends_field;

$pickerDate = '';
if(!empty($query[$ends_field])) {
// Adjust the time back to the user's timezone
$coptions['value'] = $query[$ends_field]->i18nFormat("yyyy-MM-dd HH:mm:ss", $this->get('vv_tz'));
$pickerDate = $query[$ends_field]->i18nFormat("yyyy-MM-dd", $this->get('vv_tz'));
$ends_date = \Cake\I18n\FrozenTime::parse($query[$ends_field]);
$coptions['value'] = $ends_date->i18nFormat("yyyy-MM-dd HH:mm:ss", $this->get('vv_tz'));
$pickerDate = $ends_date->i18nFormat("yyyy-MM-dd", $this->get('vv_tz'));
}

$date_args = [
Expand Down Expand Up @@ -215,7 +252,8 @@
print $this->Form->checkbox($key, [
'class' => 'form-check-input',
'checked' => $query[$key] ?? 0,
'hiddenField' => false
'hiddenField' => false,
'required' => false
]);
?>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/webroot/css/co-responsive.css
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@
margin-bottom: 0;
}
/* TOP SEARCH */
#top-filters-fields-subgroups {
.top-filters-fields-subgroups {
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: 1em;
Expand Down

0 comments on commit 33c9bca

Please sign in to comment.