diff --git a/app/plugins/CoreEnroller/src/Model/Table/EnrollmentAttributesTable.php b/app/plugins/CoreEnroller/src/Model/Table/EnrollmentAttributesTable.php index 3ca48ba2..ab46c269 100644 --- a/app/plugins/CoreEnroller/src/Model/Table/EnrollmentAttributesTable.php +++ b/app/plugins/CoreEnroller/src/Model/Table/EnrollmentAttributesTable.php @@ -165,6 +165,10 @@ public function initialize(array $config): void { 'type' => 'auxiliary', 'model' => 'CoSettings' ], + 'types' => [ + 'type' => 'auxiliary', + 'model' => 'Types' + ], ]); $this->setLayout([ 'index' => 'iframe', diff --git a/app/plugins/CoreEnroller/templates/element/field.php b/app/plugins/CoreEnroller/templates/element/field.php index 68e4be12..b5cd48d5 100644 --- a/app/plugins/CoreEnroller/templates/element/field.php +++ b/app/plugins/CoreEnroller/templates/element/field.php @@ -112,7 +112,12 @@ // HIDDEN Field // We print directly, we do not delegate to the element for further processing // In case this is a hidden field, we need to get only the value - $attr->hidden && $hidden =>$this->Form->hidden($formArguments['fieldName'], ['value' => $options['default']]), - // Default use case - default => $this->element('form/listItem', ['arguments' => $formArguments]) + $attr->hidden && $hidden => $this->Form->hidden($formArguments['fieldName'], ['value' => $options['default']]), + // For the case of xxx_person_id fields, we will render the People a Picker element. + str_ends_with($attr->attribute, 'person_id') => $this->element('CoreEnroller.spa-field', [ + 'vueElementName' => 'peopleAutocomplete', + 'formArguments' => $formArguments + ]), +// Default use case + default => $this->element('form/listItem', ['arguments' => $formArguments]) }; diff --git a/app/plugins/CoreEnroller/templates/element/spa-field.php b/app/plugins/CoreEnroller/templates/element/spa-field.php new file mode 100644 index 00000000..d70069f9 --- /dev/null +++ b/app/plugins/CoreEnroller/templates/element/spa-field.php @@ -0,0 +1,61 @@ + + +
  • +
    +
    +
    + + + element('form/requiredSpan') ?> + +
    + +
    + +
    + +
    + Field->constructSPAField( + // The Default field will be used to harvest the attributes + element: $this->Field->formField(...$formArguments), + // Vue/JS element + vueElementName: $vueElementName + ) ?> +
    +
    +
  • diff --git a/app/resources/locales/en_US/field.po b/app/resources/locales/en_US/field.po index f26b666d..312f3903 100644 --- a/app/resources/locales/en_US/field.po +++ b/app/resources/locales/en_US/field.po @@ -234,6 +234,9 @@ msgstr "IP Address" msgid "required" msgstr "Required" +msgid "element_fallback" +msgstr "Element ID not provided" + msgid "role_key" msgstr "Role Key" diff --git a/app/src/View/Helper/FieldHelper.php b/app/src/View/Helper/FieldHelper.php index 8fcf3ad6..90d6c0de 100644 --- a/app/src/View/Helper/FieldHelper.php +++ b/app/src/View/Helper/FieldHelper.php @@ -73,6 +73,7 @@ class FieldHelper extends Helper { * @param array $config The configuration settings provided to this helper. * * @return void + * @since COmanage Registry v5.0.0 */ public function initialize(array $config): void { @@ -94,6 +95,7 @@ public function initialize(array $config): void * @param string $fieldName * * @return array + * @since COmanage Registry v5.0.0 */ public function calculateLabelAndDescription(string $fieldName): array { @@ -180,6 +182,7 @@ public function calculateLabelAndDescription(string $fieldName): array * Calculate the list of classes for the li element * * @return string + * @since COmanage Registry v5.0.0 */ public function calculateLiClasses(): string { @@ -219,6 +222,42 @@ public function calculateLiClasses(): string return $classes; } + /** + * Construct the SPA field element + * + * @param string $element HTML element created with the CAKEPHP HTML Helper + * @param string $vueElementName The name of the JavaScript module + * + * @return string + * @since COmanage Registry v5.0.0 + */ + public function constructSPAField(string $element, string $vueElementName): string { + // Parse the ID attribute + $regexId = '/id="(.*?)"/m'; + preg_match_all($regexId, $element, $matchesId, PREG_SET_ORDER, 0); + + // Parse the Name attribute + $regexName = '/name="(.*?)"/m'; + preg_match_all($regexName, $element, $matchesName, PREG_SET_ORDER, 0); + + // Parse the Class attribute + $regexClass = '/class="(.*?)"/m'; + preg_match_all($regexClass, $element, $matchesClass, PREG_SET_ORDER, 0); + if(!empty($matchesId[0][1]) && !empty($matchesName[0][1])) { + return $this->getView()->element($vueElementName, [ + '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' => '' + ]); + } + + // Fallback to an error element + return $this->getView()->element('elementFallback'); + } + /** * Emit a date/time form control. * This is a wrapper function for $this->control() diff --git a/app/templates/element/form/elementFallback.php b/app/templates/element/form/elementFallback.php new file mode 100644 index 00000000..feb18104 --- /dev/null +++ b/app/templates/element/form/elementFallback.php @@ -0,0 +1,32 @@ + + + diff --git a/app/templates/element/peopleAutocomplete.php b/app/templates/element/peopleAutocomplete.php index 7787bbf3..93666844 100644 --- a/app/templates/element/peopleAutocomplete.php +++ b/app/templates/element/peopleAutocomplete.php @@ -33,6 +33,7 @@ $htmlId = $htmlId ?? 'cmPersonPickerId'; $actionUrl = $actionUrl ?? []; // the url of the page to launch on select for a stand-alone picker $viewConfigParameters = $viewConfigParameters ?? []; + $containerClasses = $containerClasses ?? 'cm-autocomplete-container'; // Get the CSRF Token in JavaScript $token = $this->request->getAttribute('csrfToken'); @@ -132,7 +133,7 @@ } // Mount the component and provide a global reference for this app instance. - window. = app.mount("#-container"); + window. = app.mount("#-container"); -
    +
    diff --git a/app/webroot/css/co-base.css b/app/webroot/css/co-base.css index 38865977..05012797 100644 --- a/app/webroot/css/co-base.css +++ b/app/webroot/css/co-base.css @@ -2185,6 +2185,13 @@ td .alert { bottom: 2px; margin-right: -26px; } +.co-loading-mini-container.over-input { + display: none; + position: absolute; + right: 0.5em; + bottom: 0.7em; + z-index: 100; +} #co-loading span, #co-loading-redirect span, .co-loading-mini span { diff --git a/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js b/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js index 990378c2..209f2f16 100644 --- a/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js +++ b/app/webroot/js/comanage/components/autocomplete/cm-autocomplete-people.js @@ -177,7 +177,9 @@ export default { return data?.People?.map((item) => { return { "value": item.id, - "label": `${item?.primary_name?.given} ${item?.primary_name?.family}`, + // XXX The label is the value that autocomplete will use to update the input field value property. + "label": `${item?.primary_name?.given} ${item?.primary_name?.family} (ID: ${item?.id})`, + "fullName": `${item?.primary_name?.given} ${item?.primary_name?.family}`, "itemId": `${item?.id}`, "email": this.filterByEmailAddressType(item?.email_addresses), "emailPretty": this.shortenString(this.constructEmailCsv(this.filterByEmailAddressType(item?.email_addresses))), @@ -190,12 +192,8 @@ export default { }) }, setPerson() { - if(this.options.type == 'default') { + if(['default', 'field'].includes(this.options.type)) { 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; } 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; @@ -249,7 +247,7 @@ export default { mounted() { if(this.options.inputValue != undefined && this.options.inputValue != '' - && this.options.htmlId == 'person_id') { + && this.options.htmlId.endsWith('person_id')) { this.options.inputProps.value = `${this.options.formParams?.fullName} (ID: ${this.options.inputValue})` } }, @@ -272,11 +270,22 @@ export default { } // Otherwise return the default return this.txt['autocomplete.people.label']; + }, + hasAutoCompleteLabel: function() { + // Check to see if a label has been passed in + return this.options.label !== undefined && this.options.label !== '' + }, + getMiniLoaderClasses: function() { + if(this.options.label !== undefined && this.options.label !== '') { + return "co-loading-mini-container d-inline ms-1" + } else { + return "co-loading-mini-container d-inline ms-1 over-input" + } } }, template: ` - - + +
    - - + + + {{ this.txt['GroupMembers'] }}