Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
CFM-291_filter_groups_by_member (#196)
* Display full name on active button

* Render peoplePicker with hardcoded configuration

* add correct picker configuration

* filter with selected person. Update the picker if a value alreday exists.

* handle undefined formsData array

* move method parameters in the method

* use data attribute to pass person id
Ioannis committed May 6, 2024
1 parent b62d32c commit b0e2c87
Showing 12 changed files with 236 additions and 37 deletions.
6 changes: 5 additions & 1 deletion app/src/Lib/Traits/IndexQueryTrait.php
@@ -159,7 +159,11 @@ public function getIndexQuery(bool $pickerMode = false, array $requestParams = [
$this->viewBuilder()
->getVar('vv_tz'));

// Pass any additional field filter confiration in the view
// Extra View Variables
foreach ($table->getViewVars() as $key => $variable) {
$this->set($key, $variable);
}
// Pass any additional field filter configuration in the view
$this->set('vv_searchable_attributes_extras', $table->getSearchFiltersExtras());
if(!empty($searchableAttributes)) {
$this->set('vv_searchable_attributes', $searchableAttributes);
32 changes: 30 additions & 2 deletions app/src/Lib/Traits/SearchFilterTrait.php
@@ -52,6 +52,12 @@ trait SearchFilterTrait {
*/
private array $searchFiltersExtras = [];

/**
* List of view Vars
* @var array
*/
private array $viewVars = [];

/**
* Optional filter configuration that dictates display state and allows for related models
*
@@ -135,7 +141,7 @@ public function addJoins(Query $query, string $attribute, ServerRequest $request
* @since COmanage Registry v5.0.0
*/
public function constructDateComparisonClause(QueryExpression $exp, string $attributeWithModelPrefix, array $dates): QueryExpression {
// Both are empty, just return
// Both are empty, return
if (empty($dates[0]) && empty($dates[1])) {
return $exp;
}
@@ -243,9 +249,22 @@ public function getSearchableAttributes(string $controller, string $vv_tz=null):
}

$filterType = $f['type'] ?? 'string';
// Custom boolean use cases
if(\in_array($f['type'], ['isNull', 'isNotNull'])) {
$filterType = 'boolean';
}
// Picker configuration
if(isset($f['picker'])) {
$autocompleteArgs = [
'type' => 'default',
'fieldName' => $field,
'personType' => $f['picker']['type'],
'htmlId' => $field, // This is the input ID
'viewConfigParameters' => $f['picker']['configuration']
];
$this->viewVars['vv_autocomplete_arguments'] = $autocompleteArgs;
}

$this->searchFilters[$field] = [
'type' => $filterType,
'label' => $f['label'] ?? StringUtilities::columnKey($fieldName, $field, $vv_tz, true),
@@ -256,7 +275,7 @@ public function getSearchableAttributes(string $controller, string $vv_tz=null):
}

foreach ($this->filterMetadataFields() as $column => $type) {
// If the column is an array then we are accessing the Metadata fields. Skip
// If the column is an array, then we are accessing the Metadata fields. Skip
if(is_array($type)) {
continue;
}
@@ -323,4 +342,13 @@ public function getSearchFiltersExtras(): array
return $this->searchFiltersExtras;
}

/**
* Get View Vars
*
* @since COmanage Registry v5.0.0
*/
public function getViewVars(): array
{
return $this->viewVars;
}
}
26 changes: 26 additions & 0 deletions app/src/Model/Table/GroupsTable.php
@@ -145,6 +145,16 @@ public function initialize(array $config): void {
'groupTypes' => [
'type' => 'enum',
'class' => 'GroupTypeEnum'
],
// Required for peoplePicker
'cosettings' => [
'type' => 'auxiliary',
'model' => 'CoSettings'
],
// Required for peoplePicker
'types' => [
'type' => 'auxiliary',
'model' => 'Types'
]
]);

@@ -154,6 +164,22 @@ public function initialize(array $config): void {
'model' => 'Identifiers',
'active' => true,
'order' => 4
],
'person_id' => [
'type' => 'integer',
'model' => 'GroupMembers',
'active' => true,
'order' => 5,
'picker' => [
'type' => 'person',
'configuration' => [
// For the Groups Filtering block we want to
// pick/GET from the entire CO pool of people
'action' => 'GET',
// The co configuration will fall throught the default configuration
'for' => 'co'
]
]
]
]);

34 changes: 34 additions & 0 deletions app/src/View/Helper/FieldHelper.php
@@ -168,6 +168,40 @@ public function calculateLabelAndDescription(string $fieldName): array
return [$label, $desc];
}

/**
* Calculate the list of classes for the li element
*
* @return string
*/
public function calculateLiClasses(): string
{
$fieldName = $this->getView()->get('fieldName');
$vv_field_arguments = $this->getView()->get('vv_field_arguments');

// Class calculation by field Type
$classes = match ($this->getFieldType($fieldName)) {
'date',
'datetime',
'timestamp' => 'fields-datepicker ',
default => ''
};

// Class calculation by field name
$classes .= match ($fieldName) {
'source_record' => 'source-record ',
'retry_interval',
'login' => 'subfield ',
default => ''
};

// Class calculation by type of Info Div
if(isset($vv_field_arguments['autocomplete'])) {
$classes .= 'fields-people-autocomplete ';
}

return $classes;
}

/**
* Emit a date/time form control.
* This is a wrapper function for $this->control()
18 changes: 18 additions & 0 deletions app/src/View/Helper/FilterHelper.php
@@ -30,6 +30,7 @@
namespace App\View\Helper;

use Cake\Collection\Collection;
use Cake\ORM\TableRegistry;
use Cake\Utility\{Inflector, Hash};
use Cake\View\Helper;

@@ -157,4 +158,21 @@ public function getHiddenFields(): array
->filter(fn($value, $key) => !\in_array($key, $searchable_parameters, true) && $key != 'page')
->toArray();
}

/**
* Construct Full Name from Person ID
*
* @param int $personId
*
* @return string
*/
public function getFullName(int $personId): string
{
if(empty($personId)) {
return '';
}
$ModelTable = TableRegistry::getTableLocator()->get('Names');
$person = $ModelTable->primaryName($personId);
return "{$person->given} {$person->family}";
}
}
26 changes: 25 additions & 1 deletion app/templates/element/filter/filter.php
@@ -27,8 +27,23 @@

declare(strict_types = 1);

use Cake\Utility\Inflector;
?>

<script type="text/javascript">
$(function() {
// Remove the friendly representation of the person_id input element before submiting
const filterForm = document.getElementById("top-filters-form");
filterForm.addEventListener('formdata', (event) => {
if(event.formData.has('person_id')) {
const personId = $(filterForm).find('#person_id')[0].getAttribute('datapersonid')
event.formData.set('person_id', personId)
}
});
});
</script>

<?php
use Cake\Utility\Inflector;

// $this->name = Models
$modelsName = $this->name;
@@ -68,8 +83,17 @@
<?php
foreach($field_generic_columns as $key => $options) {
$elementArguments = compact('options', 'key');
// Set in SearchfilterTrait.php
if($vv_autocomplete_arguments['fieldName'] === $key) {
// Picker is a custom type.
// This is why we calculate it here, and we
// only use it to pick the correct element
$options['type'] = 'picker';
}

print match($options['type']) {
'date' => $this->element('filter/dateSingle', $elementArguments),
'picker' => $this->element('filter/peoplePicker', $elementArguments),
default => $this->element('filter/default', $elementArguments),
};
}
65 changes: 65 additions & 0 deletions app/templates/element/filter/peoplePicker.php
@@ -0,0 +1,65 @@
<?php
/**
* COmanage Registry People Picker
*
* Portions licensed to the University Corporation for Advanced Internet
* Development, Inc. ("UCAID") under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* UCAID licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @link https://www.internet2.edu/comanage COmanage Project
* @package registry
* @since COmanage Registry v5.0.0
* @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
*/


/*
* Parameters:
* $columns : array, required
* $key : string, required
* $options : array, required
*/

declare(strict_types = 1);

use Cake\Utility\{Inflector};

// $columns = the passed parameter $indexColumns as found in columns.inc; provides overrides for labels and sorting.
$columns = $vv_indexColumns;

$wrapperCssClass = 'filter-active';
if(empty($options['active'])) {
$wrapperCssClass = 'filter-inactive';
}

$label = Inflector::humanize(
Inflector::underscore(
$options['label'] ?? $columns[$key]['label']
)
);

// Get the Field configuration
$formParams = $this->Filter->calculateFieldParams($key, $label);
if(!empty($formParams['value']) && $key == 'person_id') {
$formParams['fullName'] = $this->Filter->getFullName((int)$formParams['value']);
}
$vv_autocomplete_arguments['formParams'] = $formParams;

?>

<div class="filter-standard <?= $wrapperCssClass ?>">
<?= $this->element('peopleAutocomplete', $vv_autocomplete_arguments) ?>
</div>
6 changes: 5 additions & 1 deletion app/templates/element/filter/topButtons.php
@@ -41,10 +41,12 @@
$populated_vvar = lcfirst(Inflector::pluralize(Inflector::camelize($key)));
$button_label = 'Range';
if(isset($$populated_vvar) && isset($$populated_vvar[$params])) {
// Get label name from AutoViewPopulated vars
$button_label = $$populated_vvar[$params];
} elseif(!is_array($params)) {
$button_label = $params;
if(isset($vv_searchable_attributes_extras)) {
// Extras use case
if(!empty($vv_searchable_attributes_extras)) {
$flattenedSearchableAttributesExtras = Hash::flatten($vv_searchable_attributes_extras);
$filteredFlattenedSearchableAttributesExtras = array_filter(
$flattenedSearchableAttributesExtras,
@@ -54,6 +56,8 @@
if(!empty($filteredFlattenedSearchableAttributesExtras)) {
$button_label = array_pop($filteredFlattenedSearchableAttributesExtras);
}
} elseif ($key == 'person_id') {
$button_label = $this->Filter->getFullName((int)$params);
}
}

24 changes: 1 addition & 23 deletions app/templates/element/form/listItem.php
@@ -38,28 +38,6 @@
$fieldName = $arguments['fieldName'];
$this->set('vv_field_arguments', $arguments);

// Class calculation by field Type
$classes = match ($this->Field->getFieldType($fieldName)) {
'date',
'datetime',
'timestamp' => 'fields-datepicker ',
default => ''
};

// Class calculation by field name
$classes .= match ($fieldName) {
'source_record' => 'source-record ',
'retry_interval',
'login' => 'subfield ',
default => ''
};

// Class calculation by type of Info Div
if(isset($arguments['autocomplete'])) {
$classes .= 'fields-people-autocomplete ';
}


// If an attribute is frozen, inject a special link to unfreeze it, since
// the attribute is read-only and the admin can't simply uncheck the setting
if($fieldName == 'frozen' && $this->Field->getEntity()->frozen) {
@@ -101,6 +79,6 @@

?>

<li class="<?= trim($classes) ?>">
<li class="<?= trim($this->Field->calculateLiClasses()) ?>">
<?= $this->element('form/fieldDiv')?>
</li>
2 changes: 1 addition & 1 deletion app/templates/element/javascript.php
@@ -175,7 +175,7 @@
$('#top-filters-submit').addClass("tss-rebalance");
} else {
$('#top-filters-submit').removeClass("tss-rebalance");
}
}
}
});

12 changes: 10 additions & 2 deletions app/templates/element/peopleAutocomplete.php
@@ -27,7 +27,7 @@

// Get parameters
$type = $type ?? 'stand-alone'; // autocomplete person picker type: 'stand-alone' or 'field', defaults to 'stand-alone'.
$label = $label ?? __d('operation','autocomplete.people.label');
$label = $label ?? $formParams['label'] ?? __d('operation','autocomplete.people.label');
$fieldName = $fieldName ?? 'person_id';
$personType = $personType ?? 'coperson';
$htmlId = $htmlId ?? 'cmPersonPickerId';
@@ -38,6 +38,7 @@
$token = $this->request->getAttribute('csrfToken');
// Load my helper functions
$vueHelper = $this->loadHelper('Vue');
$inputValue = $inputValue ?? $formParams['value'] ?? '';

// If we have the $actionUrl array, construct the URL
$constructedActionUrl = '';
@@ -80,7 +81,14 @@
personType: '<?= $personType ?>',
minLength: 2, // XXX probably should be set by config and default to 3
htmlId: '<?= $htmlId ?>',
actionUrl: '<?= $constructedActionUrl ?>'
actionUrl: '<?= $constructedActionUrl ?>',
inputValue: '<?= $inputValue ?>',
inputProps: {
name: '<?= $htmlId ?>',
// This is not translated to data-personid but to datapersonid.
dataPersonid: '<?= $inputValue ?>'
},
formParams: <?= json_encode($formParams ?? []) ?>,
},
error: ''
}
@@ -159,10 +159,12 @@ export default {
})
},
setPerson() {
if(this.options.type == 'field') {
if(this.options.type == 'default') {
this.options.inputProps.dataPersonid = this.person.value
} else if(this.options.type == 'field') {
// The picker is part of a standard form field
const field = document.getElementById(this.options.fieldName);
field.value = this.person.value;
field.value = this.person.value;
} else {
// The picker is stand-alone, and should render the configured page in a modal on @item-select
const urlForModal = this.options.actionUrl + '&person_id=' + this.person.value;
@@ -180,6 +182,13 @@ export default {
return str
}
},
mounted() {
if(this.options.inputValue != undefined
&& this.options.inputValue != ''
&& this.options.htmlId == 'person_id') {
this.options.inputProps.value = `${this.options.formParams?.fullName} (ID: ${this.options.inputValue})`
}
},
computed: {
hasMorePages: function() {
if(this.rawData.responseMeta !== undefined) {
@@ -206,17 +215,18 @@ export default {
<MiniLoader :isLoading="loading" classes="co-loading-mini-container d-inline ms-1"/>
<AutoComplete
v-model="person"
@complete="searchPeople"
inputClass="cm-autocomplete"
:inputId="this.options.htmlId"
:inputProps="this.options.inputProps"
:placeholder="this.txt['autocomplete.people.placeholder']"
panelClass="cm-autocomplete-panel"
:inputId="this.options.htmlId"
:suggestions="this.people"
optionLabel="label"
:minLength="this.options.minLength"
:placeholder="this.txt['autocomplete.people.placeholder']"
:delay="500"
loadingIcon=null
:suggestions="this.people"
forceSelection
@complete="searchPeople"
@item-select="setPerson">
<template #option="slotProps">
<div class="cm-ac-item">

0 comments on commit b0e2c87

Please sign in to comment.