Skip to content

Commit

Permalink
Afform - Quick add links for Autocomplete fields
Browse files Browse the repository at this point in the history
  • Loading branch information
colemanw committed Oct 23, 2023
1 parent a1ea906 commit cb5eef6
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 10 deletions.
3 changes: 3 additions & 0 deletions CRM/Core/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -2324,6 +2324,9 @@ public function addAutocomplete(string $name, string $label = '', array $props =
$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);
}
Expand Down
37 changes: 37 additions & 0 deletions CRM/Core/Resources.php
Original file line number Diff line number Diff line change
Expand Up @@ -432,10 +432,47 @@ public static function renderL10nJs(GenericHookEvent $e) {
'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 $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?
Expand Down
18 changes: 18 additions & 0 deletions ang/afform/afformQuickAddIndividual.aff.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<af-form ctrl="afform">
<af-entity data="{contact_type: 'Individual'}" type="Individual" name="Individual1" actions="{create: true, update: false}" security="RBAC" />
<fieldset af-fieldset="Individual1" class="af-container">
<div class="af-container">
<div class="af-container af-layout-inline">
<af-field name="first_name" />
<af-field name="middle_name" />
<af-field name="last_name" />
</div>
<div af-join="Email" data="{is_primary: true}">
<div class="af-container af-layout-inline">
<af-field name="email" />
</div>
</div>
</div>
</fieldset>
<button class="af-button btn btn-primary" crm-icon="fa-check" ng-click="afform.submit()">Submit</button>
</af-form>
14 changes: 14 additions & 0 deletions ang/afform/afformQuickAddIndividual.aff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

return [
'type' => '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,
];
2 changes: 2 additions & 0 deletions ang/crmUi.js
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,7 @@
crmAutocompleteParams: '<',
multi: '<',
autoOpen: '<',
quickAdd: '<',
staticOptions: '<'
},
link: function(scope, element, attr, ctrl) {
Expand Down Expand Up @@ -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,
});
});
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@
<input crm-ui-select="{data: $ctrl.editor.securityModes}" ng-model="getSet('security')" ng-model-options="{getterSetter: true}" class="form-control">
</div>
</li>
<li ng-if="$ctrl.fieldDefn.input_type === 'EntityRef'" title="{{:: ts('Allow a new entity to be created via quick-add popup') }}">
<div href ng-click="$event.stopPropagation()" class="af-gui-field-select-in-dropdown">
<input crm-ui-select="{data: $ctrl.quickAddLinks, multiple: true, placeholder: ts('Quick Add')}" ng-model="getSet('input_attrs.quickAdd')" ng-model-options="{getterSetter: true}" class="form-control">
</div>
</li>
<li ng-if="$ctrl.fieldDefn.input_type === 'EntityRef'">
<a href ng-click="toggleAttr('input_attrs.autoOpen'); $event.stopPropagation(); $event.target.blur();" title="{{:: ts('Show autocomplete results without typing') }}">
<i class="crm-i fa-{{ getProp('input_attrs.autoOpen') ? 'check-' : '' }}square-o"></i>
Expand Down
13 changes: 13 additions & 0 deletions ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions ext/afform/core/ang/af/fields/EntityRef.html
Original file line number Diff line number Diff line change
Expand Up @@ -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()" >
73 changes: 63 additions & 10 deletions js/Common.js
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,23 @@ if (!CRM.vars) CRM.vars = {};
});
}

function getStaticOptionMarkup(staticItems) {
function renderQuickAddMarkup(quickAddLinks) {
if (!quickAddLinks || !quickAddLinks.length) {
return '';
}
let markup = '<div class="crm-entityref-links crm-entityref-quick-add">';
CRM.config.quickAdd.forEach((link) => {
if (quickAddLinks.includes(link.path)) {
markup += ' <a class="crm-hover-button" href="' + _.escape(CRM.url(link.path)) + '">' +
'<i class="crm-i ' + _.escape(link.icon) + '" aria-hidden="true"></i> ' +
_.escape(link.title) + '</a>';
}
});
markup += '</div>';
return markup;
}

function renderStaticOptionMarkup(staticItems) {
if (!staticItems.length) {
return '';
}
Expand All @@ -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: {
Expand Down Expand Up @@ -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', () => {
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', () => {
let id = $(this).attr('href').substring(1),
item = _.findWhere(staticItems, {id: id});
$el.select2('close');
if (multiple) {
Expand All @@ -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', () => {
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;
});
});
});
Expand Down
1 change: 1 addition & 0 deletions templates/CRM/common/l10n.js.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -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});
Expand Down

0 comments on commit cb5eef6

Please sign in to comment.