Skip to content

Commit

Permalink
Fix_person_roles_picker (#275)
Browse files Browse the repository at this point in the history
* Fix person roles people picker

* Fix person_id in beforeMarshal

* Outline one approach to handling the people picker of type 'field'

* Add link to selected Person Canvas when a people picker arrives with a value.

* Fix peoplePicker inconsistencies

* Fix icon.Fix postfix rendering condition.

---------

Co-authored-by: Arlen Johnson <arlen@sphericalcowgroup.com>
  • Loading branch information
Ioannis and arlen authored Feb 4, 2025
1 parent 650dfc9 commit e6a5002
Show file tree
Hide file tree
Showing 15 changed files with 327 additions and 156 deletions.
3 changes: 3 additions & 0 deletions app/resources/locales/en_US/menu.po
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ msgstr "Toggle menu collapse button"
msgid "options"
msgstr "Options"

msgid "person.canvas"
msgstr "Person Canvas"

msgid "registries"
msgstr "Available {0} Registries"

Expand Down
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 @@ -57,6 +57,9 @@ msgstr "show more"
msgid "autocomplete.people.desc"
msgstr "Begin typing to find a person (use at least {0} characters from a name, email address, or identifier)"

msgid "autocomplete.people.field.desc"
msgstr "Begin typing to find a person (use characters from a name, email address, or identifier)"

msgid "autocomplete.people.label"
msgstr "Search for a person"

Expand Down
2 changes: 1 addition & 1 deletion app/src/Lib/Traits/SearchFilterTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ public function getSearchableAttributes(string $controller, \DateTimeZone $vv_tz
// Picker configuration
if(isset($f['picker'])) {
$autocompleteArgs = [
'type' => 'default',
'type' => 'search',
'fieldName' => $field,
'personType' => $f['picker']['type'],
'htmlId' => $field, // This is the input ID
Expand Down
15 changes: 15 additions & 0 deletions app/src/Lib/Util/StringUtilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,21 @@ public static function foreignKeyToClassName(string $s): string {
return Inflector::camelize(Inflector::pluralize(substr($s, 0, strlen($s)-3)));
}

/**
* Determine the controller name from a foreign key (eg: report_id -> reports).
*
* @since COmanage Registry v5.1.0
* @param string $s Foreign Key name
* @return string Class name
*/

public static function foreignKeyToController(string $s): string {
if($s === 'affiliation_type_id') {
$s = 'type_id';
}
return Inflector::underscore(Inflector::pluralize(substr($s, 0, strlen($s)-3)));
}

/**
* Localize a controller name, accounting for plugins.
*
Expand Down
11 changes: 11 additions & 0 deletions app/src/Model/Table/PersonRolesTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,17 @@ public function beforeMarshal(EventInterface $event, \ArrayObject $data, \ArrayO
}
}
}

$re = '/^.*\(ID: (\d+)\)$/m';
if(!empty($data['sponsor_person_id'])) {
preg_match_all($re, $data['sponsor_person_id'], $matchesSponsor, PREG_SET_ORDER, 0);
$data['sponsor_person_id'] = $matchesSponsor[0][1];
}

if(!empty($data['manager_person_id'])) {
preg_match_all($re, $data['manager_person_id'], $matchesManager, PREG_SET_ORDER, 0);
$data['manager_person_id'] = $matchesManager[0][1];
}
}

/**
Expand Down
21 changes: 11 additions & 10 deletions app/src/Model/Table/PetitionHistoryRecordsTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,29 +132,30 @@ public function generateDisplayField(\App\Model\Entity\JobHistoryRecord $entity)

return __d('controller', 'PetitionHistoryRecords', [1]);
}

/**
* Record a Petition History Record.
*
* @since COmanage Registry v5.0.0
* @param int $petitionId Petition ID
* @param string $enrollmentFlowStepId Enrollment Flow Step ID, or null for start or finalize
* @param string $action PetitionActionEnum
* @param string $comment Comment
* @param int $actorPersonId Actor Person ID
* @param int $petitionId Petition ID
* @param int|null $enrollmentFlowStepId Enrollment Flow Step ID, or null for start or finalize
* @param string $action PetitionActionEnum
* @param string $comment Comment
* @param int|null $actorPersonId Actor Person ID
*
* @return int Petition History Record ID
* @since COmanage Registry v5.0.0
*/

public function record(
int $petitionId,
?int $enrollmentFlowStepId=null,
?int $enrollmentFlowStepId,
string $action,
string $comment,
?int $actorPersonId=null
?int $actorPersonId = null
): int {
$obj = $this->newEntity([
'petition_id' => $petitionId,
'enrollment_flow_step_id' => $enrollmentFlowStepId,
'enrollment_flow_step_id' => $enrollmentFlowStepId ?? null,
'action' => $action,
'comment' => $comment,
'actor_person_id' => $actorPersonId
Expand Down
88 changes: 77 additions & 11 deletions app/src/View/Helper/FieldHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@

namespace App\View\Helper;

use App\Lib\Enum\DateTypeEnum;
use App\Lib\Util\StringUtilities;
use Cake\I18n\FrozenTime;
use Cake\Utility\Inflector;
use Cake\View\Helper;
use App\Lib\Enum\DateTypeEnum;
use App\Lib\Util\StringUtilities;
use DOMDocument;

class FieldHelper extends Helper {
public $helpers = ['Form', 'Html'];
Expand Down Expand Up @@ -243,15 +244,31 @@ public function constructSPAField(string $element, string $vueElementName): stri
// Parse the Class attribute
$regexClass = '/class="(.*?)"/m';
preg_match_all($regexClass, $element, $matchesClass, PREG_SET_ORDER, 0);

// Parse the Value attribute
// XXX This will not work properly if the input element is a select element
if (!empty($matchesClass[0][1])
&& !str_contains($matchesClass[0][1], 'select')
) {
$regexClass = '/value="(.*?)"/m';
preg_match_all($regexClass, $element, $matchesValue, PREG_SET_ORDER, 0);
}

if(!empty($matchesId[0][1]) && !empty($matchesName[0][1])) {
return $this->getView()->element($vueElementName, [
$vueElementProperties = [
'htmlId' => $matchesId[0][1],
'fieldName' => $matchesName[0][1],
'containerClasses' => $matchesClass[0][1],
'type' => 'field',
// we want the label to be an empty string to hide the default label introduced by the module.
'label' => ''
]);
];

if (isset($matchesValue[0][1])) {
$vueElementProperties['inputValue'] = $matchesValue[0][1];
}

return $this->getView()->element($vueElementName, $vueElementProperties);
}

// Fallback to an error element
Expand Down Expand Up @@ -279,14 +296,18 @@ public function dateField(string $fieldName,
$dateTitle = $dateType === DateTypeEnum::DateOnly ? 'datepicker.enterDate' : 'datepicker.enterDateTime';
$datePattern = $dateType === DateTypeEnum::DateOnly ? '\d{4}-\d{2}-\d{2}' : '\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}';
$queryParams = $this->getView()->getRequest()->getQueryParams();
$date_object = !empty($queryParams[$fieldName])
? FrozenTime::parse($queryParams[$fieldName])
: $this->getEntity()?->$fieldName;

// Petition Attribute Collection use case
if($date_object === null && !empty($fieldArgs['default'])) {
$date_object = $fieldArgs['default'];
}
$date_object = match(true) {
// filtering block
!empty($queryParams[$fieldName]) => FrozenTime::parse($queryParams[$fieldName]),
// Petition View/ Value saved as string
isset($fieldArgs['default']) && is_string($fieldArgs['default']) => FrozenTime::parse($fieldArgs['default']),
// Petition View/ Value saved a FronzenTime
isset($fieldArgs['default'])
&& is_a($fieldArgs['default'], 'Cake\I18n\FrozenTime') => $fieldArgs['default'],
// Table record/ Retrieve it from the Entity object
default => $this->getEntity()?->$fieldName,
};
// Create the options array for the (text input) form control
$coptions = [];

Expand Down Expand Up @@ -397,6 +418,8 @@ public function formField(string $fieldName,
|| ($fieldName == 'plugin' && $this->action == 'edit');

// Selects, Checkboxes, and Radio Buttons use "disabled"
// XXX For this use case we need to add a hidden input field. If we do not we will not be able
// to post the value
$fieldArgs['disabled'] = $fieldArgs['readonly'];

// required can be overridden by the fields.inc, but start with the default expectation
Expand Down Expand Up @@ -625,4 +648,47 @@ public function sourceLink($entity): string
return $link;
}

/**
* Iterate over form arguments, parse the generated HTML element using XMLReader,
* and inject a hidden input field if the element (e.g., select, checkbox, radio) is disabled.
* Finally, outputs the original HTML element.
*
* @param string $element
* @param array $formArguments An array containing options/attributes for the HTML form element.
* @return void
* @since COmanage Registry v5.1.0
*/
public function getElementsForDisabledInput(string $element, array $formArguments): void
{
$orginalElement = $this->getView()->element($element, ['arguments' => $formArguments]);
if ($orginalElement) {
$htmlObj = new DOMDocument();
$htmlObj->loadHTML($orginalElement, LIBXML_NOERROR);
if($htmlObj->getElementsByTagName('select')->length > 0) {
// Check if it is disabled. If it is then print a hidden element
foreach($htmlObj->getElementsByTagName('select')->item(0)->attributes as $attr) {
if($attr->name == 'disabled' && $attr->value == 'disabled') {
print $this->getView()->Form->hidden($formArguments['fieldName'], ['value' => $formArguments["fieldOptions"]["default"]]);
}
}
} elseif ($htmlObj->getElementsByTagName('radio')->length) {
// Check if it is disabled. If it is then print a hidden element
foreach($htmlObj->getElementsByTagName('radio')->item(0)->attributes as $attr) {
if($attr->name == 'disabled' && $attr->value == 'disabled') {
print $this->getView()->Form->hidden($formArguments['fieldName'], ['value' => $formArguments["fieldOptions"]["default"]]);
}
}
} elseif ($htmlObj->getElementsByTagName('checkbox')->length) {
// Check if it is disabled. If it is then print a hidden element
foreach($htmlObj->getElementsByTagName('checkbox')->item(0)->attributes as $attr) {
if($attr->name == 'disabled' && $attr->value == 'disabled') {
print $this->getView()->Form->hidden($formArguments['fieldName'], ['value' => $formArguments["fieldOptions"]["default"]]);
}
}
}
}

// Print the original element
print $orginalElement;
}
}
53 changes: 51 additions & 2 deletions app/src/View/Helper/PetitionHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,9 @@
namespace App\View\Helper;

use App\Lib\Util\StringUtilities;
use App\Lib\Util\TableUtilities;
use Cake\ORM\Table;
use Cake\ORM\TableRegistry;
use Cake\Utility\Inflector;
use Cake\Validation\Validator;
use Cake\View\Helper;
use CoreEnroller\Model\Table\EnrollmentAttributesTable;

Expand Down Expand Up @@ -95,4 +93,55 @@ public function getTable(string $tableName): Table
{
return TableRegistry::getTableLocator()->get($tableName);
}

/**
* Fetch a record by its ID and optionally include related data.
*
* This method retrieves a specific record from the database using its ID
* and foreign key. If optional related data (associations) need to be loaded,
* they can be specified with the `$contains` parameter.
*
* @param string $foreignKey
* @param int|string $id
* @param array $contains
*
* @return array
* @since COmanage Registry v5.1.0
*/
public function getRecordForId(string $foreignKey, int|string $id, array $contains = []): array
{
$tableName = StringUtilities::foreignKeyToClassName($foreignKey);
if($tableName === 'AffiliationTypes') {
$tableName = 'Types';
}
$table = $this->getTable($tableName);
$query = $table->find()
->where([$tableName . '.id' => $id]);
if(!empty($contains)) {
return $query
->contain($contains)
->first()
->toArray();
}
return $query->first()->toArray();
}

/**
* Transform an enrollment attribute name into a class postfix
*
* This method modifies the given attribute name by converting it
* to a format suitable for use as a CSS class postfix.
*
* @param string $attributeName Attribute name to transform
* @return string Transformed class postfix
* @since COmanage Registry v5.1.0
*/
public function getClassPostfixFromAttributeName(string $attributeName): string
{
if(str_ends_with($attributeName, '_id')) {
$attributeName = substr($attributeName, 0, -3);
}
$attributeName = Inflector::underscore($attributeName);
return str_replace('_', '-', $attributeName);
}
}
8 changes: 8 additions & 0 deletions app/src/View/Helper/VueHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,25 @@ class VueHelper extends Helper {
'report.for',
'value.copied',
],
'menu' => [
'person.canvas'
],
'operation' => [
'add',
'add.member',
'add.owner',
'autocomplete.pager.show.more',
'autocomplete.people.desc',
'autocomplete.people.field.desc',
'autocomplete.people.label',
'autocomplete.people.placeholder',
'close',
'copy',
'copy.value',
'edit',
'primary',
'remove',
'view',
'visit.link',
],
'result' => [
Expand Down
28 changes: 8 additions & 20 deletions app/templates/PersonRoles/fields.inc
Original file line number Diff line number Diff line change
Expand Up @@ -59,29 +59,11 @@ if($vv_action == 'add' || $vv_action == 'edit' || $vv_action == 'view') {
// For now, we render sponsor and manager as read only.
// XXX Need People Picker (CFM-150)
foreach(['sponsor', 'manager'] as $f) {
$fp = $f."_person";

$fname = "";
$flink = [];

if(!empty($vv_obj->$fp->names[0])) {
$fname = $vv_obj->$fp->names[0]->full_name;
$fid = $vv_obj->$fp->id;
$flink = ['url' => ['controller' => 'people', 'action' => 'edit', $vv_obj->$fp->id]];
$formParams = [
'value' => $fid,
'fullName' => $fname,
'link' => $flink,
];
$this->set('formParams', $formParams);
}

$fp = $f . '_person';

$vv_autocomplete_arguments = [
'fieldName' => $f.'_person_id',
'status' => $fname,
'link' => $flink,
'fieldLabel' => __d('field', $f),
'fieldOptions' => [],
'autocomplete' => [
'configuration' => [
'action' => 'GET',
Expand All @@ -90,6 +72,12 @@ if($vv_action == 'add' || $vv_action == 'edit' || $vv_action == 'view') {
]
];

if(!empty($vv_obj->$fp->names[0])) {
$vv_autocomplete_arguments['fieldOptions'] = [
'default' => $vv_obj->$fp->id
];
}

print $this->element('form/listItem', ['arguments' => $vv_autocomplete_arguments]);
}

Expand Down
8 changes: 3 additions & 5 deletions app/templates/element/filter/peoplePicker.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,9 @@
}

// 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;
$vv_autocomplete_arguments['fieldOptions'] = $this->Filter->calculateFieldParams($key, $label);
// Update the view var
$this->set('vv_autocomplete_arguments', $vv_autocomplete_arguments);

?>

Expand Down
Loading

0 comments on commit e6a5002

Please sign in to comment.