From 4f42de16f8320b3fba855aecc29e8acfd183a8e4 Mon Sep 17 00:00:00 2001 From: colemanw Date: Tue, 26 Sep 2023 19:56:35 -0400 Subject: [PATCH] Afform - Quick add links for Autocomplete fields --- CRM/Core/Form.php | 3 + CRM/Core/Resources.php | 37 ++++++++++ ang/afform/afformQuickAddIndividual.aff.html | 18 +++++ ang/afform/afformQuickAddIndividual.aff.php | 14 ++++ .../afformQuickAddOrganization.aff.html | 16 ++++ ang/afform/afformQuickAddOrganization.aff.php | 14 ++++ ang/crmUi.js | 2 + .../afGuiEditor/elements/afGuiField-menu.html | 5 ++ .../elements/afGuiField.component.js | 13 ++++ ext/afform/core/ang/af/fields/EntityRef.html | 1 + js/Common.js | 73 ++++++++++++++++--- templates/CRM/common/l10n.js.tpl | 1 + 12 files changed, 187 insertions(+), 10 deletions(-) create mode 100644 ang/afform/afformQuickAddIndividual.aff.html create mode 100644 ang/afform/afformQuickAddIndividual.aff.php create mode 100644 ang/afform/afformQuickAddOrganization.aff.html create mode 100644 ang/afform/afformQuickAddOrganization.aff.php diff --git a/CRM/Core/Form.php b/CRM/Core/Form.php index 0cffa991c2..3a04c3bb23 100644 --- a/CRM/Core/Form.php +++ b/CRM/Core/Form.php @@ -2324,6 +2324,9 @@ class CRM_Core_Form extends HTML_QuickForm_Page { $props['data-select-params'] = json_encode($props['select']); $props['data-api-params'] = json_encode($props['api']); $props['data-api-entity'] = $props['entity']; + if (!empty($props['select']['quickAdd'])) { + Civi::service('angularjs.loader')->addModules(['af']); + } CRM_Utils_Array::remove($props, 'select', 'api', 'entity'); return $this->add('text', $name, $label, $props, $required); } diff --git a/CRM/Core/Resources.php b/CRM/Core/Resources.php index f1610b35f6..748aaa1c26 100644 --- a/CRM/Core/Resources.php +++ b/CRM/Core/Resources.php @@ -432,10 +432,47 @@ class CRM_Core_Resources implements CRM_Core_Resources_CollectionAdderInterface 'contactSearch' => json_encode(!empty($params['includeEmailInName']) ? ts('Search by name/email or id...') : ts('Search by name or id...')), 'otherSearch' => json_encode(ts('Enter search term or id...')), 'entityRef' => self::getEntityRefMetadata(), + 'quickAdd' => self::getQuickAddForms($e->params['cid']), ]; $e->content = CRM_Core_Smarty::singleton()->fetchWith('CRM/common/l10n.js.tpl', $params); } + /** + * Gets links to "Quick Add" forms, for use in Autocomplete widgets + * + * @param int|null $cid + * @return array + */ + private static function getQuickAddForms(?int $cid): array { + $forms = []; + try { + $contactTypes = CRM_Contact_BAO_ContactType::getAllContactTypes(); + $routes = \Civi\Api4\Route::get(FALSE) + ->addSelect('path', 'title', 'access_arguments') + ->addWhere('path', 'LIKE', 'civicrm/quick-add/%') + ->execute(); + foreach ($routes as $route) { + // Ensure user has permission to use the form + if (!empty($route['access_arguments'][0]) && !CRM_Core_Permission::check($route['access_arguments'][0], $cid)) { + continue; + } + // Ensure API entity exists + [, , $entityType] = array_pad(explode('/', $route['path']), 3, '*'); + if (\Civi\Api4\Utils\CoreUtil::entityExists($entityType)) { + $forms[] = [ + 'entity' => $entityType, + 'path' => $route['path'], + 'title' => $route['title'], + 'icon' => \Civi\Api4\Utils\CoreUtil::getInfoItem($entityType, 'icon'), + ]; + } + } + } + catch (CRM_Core_Exception $e) { + } + return $forms; + } + /** * @return bool * is this page request an ajax snippet? diff --git a/ang/afform/afformQuickAddIndividual.aff.html b/ang/afform/afformQuickAddIndividual.aff.html new file mode 100644 index 0000000000..db7d08d399 --- /dev/null +++ b/ang/afform/afformQuickAddIndividual.aff.html @@ -0,0 +1,18 @@ + + +
+
+
+ + + +
+
+
+ +
+
+
+
+ +
diff --git a/ang/afform/afformQuickAddIndividual.aff.php b/ang/afform/afformQuickAddIndividual.aff.php new file mode 100644 index 0000000000..15a06fc2ce --- /dev/null +++ b/ang/afform/afformQuickAddIndividual.aff.php @@ -0,0 +1,14 @@ + 'form', + 'title' => ts('New Individual'), + 'icon' => 'fa-list-alt', + 'server_route' => 'civicrm/quick-add/Individual', + 'permission' => [ + 'add contacts', + ], + 'permission_operator' => 'AND', + 'submit_enabled' => TRUE, + 'create_submission' => FALSE, +]; diff --git a/ang/afform/afformQuickAddOrganization.aff.html b/ang/afform/afformQuickAddOrganization.aff.html new file mode 100644 index 0000000000..50a661f8a6 --- /dev/null +++ b/ang/afform/afformQuickAddOrganization.aff.html @@ -0,0 +1,16 @@ + + +
+
+
+ +
+
+
+ +
+
+
+
+ +
diff --git a/ang/afform/afformQuickAddOrganization.aff.php b/ang/afform/afformQuickAddOrganization.aff.php new file mode 100644 index 0000000000..70308a2f9f --- /dev/null +++ b/ang/afform/afformQuickAddOrganization.aff.php @@ -0,0 +1,14 @@ + 'form', + 'title' => ts('New Organization'), + 'icon' => 'fa-list-alt', + 'server_route' => 'civicrm/quick-add/Organization', + 'permission' => [ + 'add contacts', + ], + 'permission_operator' => 'AND', + 'submit_enabled' => TRUE, + 'create_submission' => FALSE, +]; diff --git a/ang/crmUi.js b/ang/crmUi.js index 718bf22b0a..811fd8101b 100644 --- a/ang/crmUi.js +++ b/ang/crmUi.js @@ -729,6 +729,7 @@ crmAutocompleteParams: '<', multi: '<', autoOpen: '<', + quickAdd: '<', staticOptions: '<' }, link: function(scope, element, attr, ctrl) { @@ -791,6 +792,7 @@ // Only auto-open if there are no static options minimumInputLength: ctrl.autoOpen && _.isEmpty(ctrl.staticOptions) ? 0 : 1, static: ctrl.staticOptions || [], + quickAdd: ctrl.quickAdd, }); }); }; diff --git a/ext/afform/admin/ang/afGuiEditor/elements/afGuiField-menu.html b/ext/afform/admin/ang/afGuiEditor/elements/afGuiField-menu.html index 9c49cbcccd..ea4c901693 100644 --- a/ext/afform/admin/ang/afGuiEditor/elements/afGuiField-menu.html +++ b/ext/afform/admin/ang/afGuiEditor/elements/afGuiField-menu.html @@ -38,6 +38,11 @@ +
  • +
    + +
    +
  • diff --git a/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js b/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js index 1b9f03495d..9e94963cf5 100644 --- a/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js +++ b/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js @@ -47,6 +47,19 @@ inputTypes.push(type); } }); + // Quick-add links for autocompletes + this.quickAddLinks = []; + let allowedEntity = (ctrl.getFkEntity() || {}).entity; + let allowedEntities = (allowedEntity === 'Contact') ? ['Individual', 'Household', 'Organization'] : [allowedEntity]; + (CRM.config.quickAdd || []).forEach((link) => { + if (allowedEntities.includes(link.entity)) { + this.quickAddLinks.push({ + id: link.path, + icon: link.icon, + text: link.title, + }); + } + }); this.searchOperators = CRM.afAdmin.search_operators; // If field has limited operators, set appropriately if (ctrl.fieldDefn.operators && ctrl.fieldDefn.operators.length) { diff --git a/ext/afform/core/ang/af/fields/EntityRef.html b/ext/afform/core/ang/af/fields/EntityRef.html index 38447c8eaf..cd415943b9 100644 --- a/ext/afform/core/ang/af/fields/EntityRef.html +++ b/ext/afform/core/ang/af/fields/EntityRef.html @@ -8,5 +8,6 @@ crm-autocomplete-params="{formName: 'afform:' + $ctrl.afFieldset.getFormName(), fieldName: $ctrl.afFieldset.getName() + ':' + $ctrl.fieldName}" multi="$ctrl.defn.input_attrs.multiple" auto-open="$ctrl.defn.input_attrs.autoOpen" + quick-add="$ctrl.defn.input_attrs.quickAdd" placeholder="{{:: $ctrl.defn.input_attrs.placeholder }}" ng-change="$ctrl.onSelectEntity()" > diff --git a/js/Common.js b/js/Common.js index f1bb534944..852540fab8 100644 --- a/js/Common.js +++ b/js/Common.js @@ -538,7 +538,23 @@ if (!CRM.vars) CRM.vars = {}; }); } - function getStaticOptionMarkup(staticItems) { + function renderQuickAddMarkup(quickAddLinks) { + if (!quickAddLinks || !quickAddLinks.length) { + return ''; + } + let markup = ''; + return markup; + } + + function renderStaticOptionMarkup(staticItems) { if (!staticItems.length) { return ''; } @@ -559,9 +575,11 @@ if (!CRM.vars) CRM.vars = {}; } select2Options = select2Options || {}; return $(this).each(function() { - var $el = $(this).off('.crmEntity'), - staticItems = getStaticOptions(select2Options.static), - multiple = !!select2Options.multiple; + const $el = $(this).off('.crmEntity'); + let staticItems = getStaticOptions(select2Options.static), + quickAddLinks = select2Options.quickAdd, + multiple = !!select2Options.multiple, + key = apiParams.key || 'id'; $el.crmSelect2(_.extend({ ajax: { @@ -604,18 +622,25 @@ if (!CRM.vars) CRM.vars = {}; } }, formatInputTooShort: function() { - var txt = _.escape($.fn.select2.defaults.formatInputTooShort.call(this)); - txt += getStaticOptionMarkup(staticItems); - return txt; + let html = _.escape($.fn.select2.defaults.formatInputTooShort.call(this)); + html += renderStaticOptionMarkup(staticItems); + html += renderQuickAddMarkup(quickAddLinks); + return html; + }, + formatNoMatches: function() { + let html = _.escape($.fn.select2.defaults.formatNoMatches); + html += renderQuickAddMarkup(quickAddLinks); + return html; } }, select2Options)); - $el.on('select2-open.crmEntity', function() { + $el.on('select2-open.crmEntity', function(){ var $el = $(this); $('#select2-drop') .off('.crmEntity') - .on('click.crmEntity', '.crm-entityref-links-static a', function(e) { - var id = $(this).attr('href').substr(1), + // Add static item to selection when clicking static links + .on('click.crmEntity', '.crm-entityref-links-static a', function() { + let id = $(this).attr('href').substring(1), item = _.findWhere(staticItems, {id: id}); $el.select2('close'); if (multiple) { @@ -628,6 +653,34 @@ if (!CRM.vars) CRM.vars = {}; $el.select2('data', item, true); } return false; + }) + // Pop-up Afform when clicking quick-add links + .on('click.crmEntity', '.crm-entityref-quick-add a', function() { + let url = $(this).attr('href'); + $el.select2('close'); + CRM.loadForm(url).on('crmFormSuccess', (e, data) => { + // Quick-add Afform has been submitted, parse submission data for id of created entity + const response = data.submissionResponse && data.submissionResponse[0]; + let createdId; + if (typeof response === 'object') { + // Loop through entities created by the afform (there should be only one) + Object.keys(response).forEach((entity) => { + if (Array.isArray(response[entity]) && response[entity][0] && response[entity][0][key]) { + createdId = response[entity][0][key]; + } + }); + } + // Update field value with new id and the widget will automatically fetch the label + if (createdId) { + if (multiple && $el.val()) { + // Select2 v3 uses a string instead of array for multiple values + $el.val($el.val() + ',' + createdId).change(); + } else { + $el.val('' + createdId).change(); + } + } + }); + return false; }); }); }); diff --git a/templates/CRM/common/l10n.js.tpl b/templates/CRM/common/l10n.js.tpl index 427ad77d5f..5e8f34e45f 100644 --- a/templates/CRM/common/l10n.js.tpl +++ b/templates/CRM/common/l10n.js.tpl @@ -24,6 +24,7 @@ CRM.config.ajaxPopupsEnabled = {$ajaxPopupsEnabled|@json_encode}; CRM.config.allowAlertAutodismissal = {$allowAlertAutodismissal|@json_encode}; CRM.config.resourceCacheCode = {$resourceCacheCode|@json_encode}; + CRM.config.quickAdd = {$quickAdd|@json_encode}; // Merge entityRef settings CRM.config.entityRef = $.extend({ldelim}{rdelim}, {$entityRef|@json_encode}, CRM.config.entityRef || {ldelim}{rdelim}); -- 2.25.1