margin-bottom: 10px;
}
-#afGuiEditor-palette-config .form-inline label {
- min-width: 110px;
-}
-
#afGuiEditor-palette-config .af-gui-entity-palette [type=search] {
width: 120px;
padding: 3px 3px 3px 5px;
$scope.entities = {};
setEditorLayout();
+ if (editor.afform.navigation) {
+ loadNavigationMenu();
+ }
+
if (editor.getFormType() === 'form') {
editor.allowEntityConfig = true;
$scope.entities = _.mapValues(afGui.findRecursive(editor.layout['#children'], {'#tag': 'af-entity'}, 'name'), backfillEntityDefaults);
}
};
+ this.toggleNavigation = function() {
+ if (editor.afform.navigation) {
+ editor.afform.navigation = null;
+ } else {
+ loadNavigationMenu();
+ editor.afform.navigation = {
+ parent: null,
+ label: editor.afform.title,
+ weight: 0
+ };
+ }
+ };
+
+ function loadNavigationMenu() {
+ if ('navigationMenu' in editor) {
+ return;
+ }
+ editor.navigationMenu = null;
+ var conditions = [
+ ['domain_id', '=', 'current_domain'],
+ ['name', '!=', 'Home']
+ ];
+ if (editor.afform.name) {
+ conditions.push(['name', '!=', editor.afform.name]);
+ }
+ crmApi4('Navigation', 'get', {
+ select: ['name', 'label', 'parent_id', 'icon'],
+ where: conditions,
+ orderBy: {weight: 'ASC'}
+ }).then(function(items) {
+ editor.navigationMenu = buildTree(items, null);
+ });
+ }
+
+ function buildTree(items, parentId) {
+ return _.transform(items, function(navigationMenu, item) {
+ if (parentId === item.parent_id) {
+ var children = buildTree(items, item.id),
+ menuItem = {
+ id: item.name,
+ text: item.label,
+ icon: item.icon
+ };
+ if (children.length) {
+ menuItem.children = children;
+ }
+ navigationMenu.push(menuItem);
+ }
+ }, []);
+ }
+
// Collects all search displays currently on the form
function getSearchDisplaysOnForm() {
var searchFieldsets = afGui.findRecursive(editor.afform.layout, {'af-fieldset': ''});
<div class="form-group" ng-class="{'has-error': !!config_form.server_route.$error.pattern}">
<label for="af_config_form_server_route">
- {{:: ts('Page') }}
+ {{:: ts('Page Route') }}
</label>
<input ng-model="editor.afform.server_route" name="server_route" class="form-control" id="af_config_form_server_route" pattern="^civicrm\/[-0-9a-zA-Z\/_]+$" onfocus="this.value = this.value || 'civicrm/'" onblur="if (this.value === 'civicrm/') this.value = ''" title="{{:: ts('Path must begin with "civicrm/"') }}" ng-model-options="editor.debounceMode">
<p class="help-block">{{:: ts('Expose the form as a standalone webpage. (Example: "civicrm/my-form")') }}</p>
</div>
<div class="form-group">
- <label>
- <input type="checkbox" ng-model="editor.afform.is_dashlet">
- {{:: ts('Add to Dashboard') }}
- </label>
- <p class="help-block">{{:: ts('Allow CiviCRM users to add the form to their home dashboard.') }}</p>
+ <div class="form-inline">
+ <label ng-class="{disabled: !editor.afform.server_route}">
+ <input type="checkbox" ng-checked="editor.afform.server_route && editor.afform.navigation" ng-disabled="!editor.afform.server_route" ng-click="editor.toggleNavigation()">
+ {{:: ts('Add to Navigation Menu') }}
+ </label>
+ <div class="form-group" ng-if="editor.afform.navigation">
+ <input class="form-control" ng-model="editor.afform.navigation.label" ng-model-options="editor.debounceMode" placeholder="{{:: ts('Title') }}" required>
+ <span ng-if="!editor.navigationMenu">
+ <input class="form-control loading" disabled crm-ui-select="{placeholder: ts('Loading menu items'), data: []}">
+ </span>
+ <span ng-if="editor.navigationMenu">
+ <input class="form-control" ng-model="editor.afform.navigation.parent"
+ crm-ui-select="{allowClear: true, placeholder: ts('Top Level'), data: editor.navigationMenu || []}">
+ </span>
+ <label for="afform-admin-navigation-weight">{{:: ts('Order') }}</label>
+ <input class="form-control" id="afform-admin-navigation-weight" type="number" placeholder="{{:: ts('Order') }}" min="0" step="1" ng-model="editor.afform.navigation.weight" required>
+ </div>
+ </div>
+ <p class="help-block disabled" ng-if="!editor.afform.server_route">{{:: ts('Requires a page route') }}</p>
+ </div>
+
+ <div class="form-group" ng-show="!!editor.afform.navigation || editor.afform.contact_summary === 'tab'">
+ <div class="form-inline">
+ <label for="afform_icon">{{:: ts('Icon') }}</label>
+ <input required id="afform_icon" ng-model="editor.afform.icon" crm-ui-icon-picker class="form-control">
+ </div>
</div>
<div class="form-group">
<option value="block">{{:: ts('As Block') }}</option>
<option value="tab">{{:: ts('As Tab') }}</option>
</select>
- <div class="form-group" ng-show="editor.afform.contact_summary === 'tab'">
- <input required ng-model="editor.afform.icon" crm-ui-icon-picker class="form-control">
- </div>
</div>
- <p class="help-block">{{:: ts('Placement can be configured using the Contact Layout Editor.') }}</p>
+ <p class="help-block" ng-show="editor.afform.contact_summary">
+ {{:: ts('Placement can be configured using the Contact Layout Editor.') }}
+ </p>
</div>
+
+ <div class="form-group">
+ <label>
+ <input type="checkbox" ng-model="editor.afform.is_dashlet">
+ {{:: ts('Add to Dashboard') }}
+ </label>
+ <p class="help-block">{{:: ts('Allow CiviCRM users to add the form to their home dashboard.') }}</p>
+ </div>
+
</fieldset>
<!-- Submit actions are only applicable to form types with a submit button (exclude blocks and search forms) -->
'name' => 'create_submission',
'data_type' => 'Boolean',
],
+ [
+ 'name' => 'navigation',
+ 'data_type' => 'Array',
+ 'description' => 'Insert into navigation menu {parent: string, label: string, weight: int}',
+ ],
[
'name' => 'layout',
'data_type' => 'Array',
return ($item[$field] ?? NULL) !== ($orig[$field] ?? NULL);
};
- // If the dashlet setting changed, managed entities must be reconciled
+ // If the dashlet or navigation setting changed, managed entities must be reconciled
+ // TODO: If this list of conditions gets any longer, then
+ // maybe we should unconditionally reconcile and accept the small performance drag.
if (
$isChanged('is_dashlet') ||
- (!empty($meta['is_dashlet']) && $isChanged('title'))
+ $isChanged('navigation') ||
+ (!empty($meta['is_dashlet']) && $isChanged('title')) ||
+ (!empty($meta['navigation']) && ($isChanged('title') || $isChanged('permission') || $isChanged('icon') || $isChanged('server_route')))
) {
\CRM_Core_ManagedEntities::singleton()->reconcile(E::LONG_NAME);
}
$result = [];
$fields = \Civi\Api4\Afform::getfields(FALSE)->setAction('create')->execute()->indexBy('name');
foreach ($fields as $fieldName => $field) {
- if (isset($params[$fieldName])) {
+ if (array_key_exists($fieldName, $params)) {
$result[$fieldName] = $params[$fieldName];
if ($field['data_type'] === 'Boolean' && !is_bool($params[$fieldName])) {
// This AfformScanner instance only lives during this method call, and it feeds off the regular cache.
$scanner = new CRM_Afform_AfformScanner();
}
+ $domains = NULL;
foreach ($scanner->getMetas() as $afform) {
- if (empty($afform['is_dashlet']) || empty($afform['name'])) {
+ if (empty($afform['name'])) {
continue;
}
- $entities[] = [
- 'module' => E::LONG_NAME,
- 'name' => 'afform_dashlet_' . $afform['name'],
- 'entity' => 'Dashboard',
- 'update' => 'always',
- // ideal cleanup policy might be to (a) deactivate if used and (b) remove if unused
- 'cleanup' => 'always',
- 'params' => [
- 'version' => 4,
- 'values' => [
- // Q: Should we loop through all domains?
- 'domain_id' => 'current_domain',
- 'is_active' => TRUE,
- 'name' => $afform['name'],
- 'label' => $afform['title'] ?? E::ts('(Untitled)'),
- 'directive' => _afform_angular_module_name($afform['name'], 'dash'),
- 'permission' => "@afform:" . $afform['name'],
- 'url' => NULL,
+ if (!empty($afform['is_dashlet'])) {
+ $entities[] = [
+ 'module' => E::LONG_NAME,
+ 'name' => 'afform_dashlet_' . $afform['name'],
+ 'entity' => 'Dashboard',
+ 'update' => 'always',
+ // ideal cleanup policy might be to (a) deactivate if used and (b) remove if unused
+ 'cleanup' => 'always',
+ 'params' => [
+ 'version' => 4,
+ 'values' => [
+ // Q: Should we loop through all domains?
+ 'domain_id' => 'current_domain',
+ 'is_active' => TRUE,
+ 'name' => $afform['name'],
+ 'label' => $afform['title'] ?? E::ts('(Untitled)'),
+ 'directive' => _afform_angular_module_name($afform['name'], 'dash'),
+ 'permission' => "@afform:" . $afform['name'],
+ 'url' => NULL,
+ ],
],
- ],
- ];
+ ];
+ }
+ if (!empty($afform['navigation']) && !empty($afform['server_route'])) {
+ $domains = $domains ?: \Civi\Api4\Domain::get(FALSE)->addSelect('id')->execute();
+ foreach ($domains as $domain) {
+ $params = [
+ 'version' => 4,
+ 'values' => [
+ 'name' => $afform['name'],
+ 'label' => $afform['navigation']['label'] ?: $afform['title'],
+ 'permission' => (array) $afform['permission'],
+ 'permission_operator' => 'OR',
+ 'weight' => $afform['navigation']['weight'] ?? 0,
+ 'url' => $afform['server_route'],
+ 'is_active' => 1,
+ 'icon' => 'crm-i ' . $afform['icon'],
+ 'domain_id' => $domain['id'],
+ ],
+ 'match' => ['domain_id', 'name'],
+ ];
+ if (!empty($afform['navigation']['parent'])) {
+ $params['values']['parent_id.name'] = $afform['navigation']['parent'];
+ }
+ $entities[] = [
+ 'module' => E::LONG_NAME,
+ 'name' => 'navigation_' . $afform['name'] . '_' . $domain['id'],
+ 'cleanup' => 'always',
+ 'update' => 'unmodified',
+ 'entity' => 'Navigation',
+ 'params' => $params,
+ ];
+ }
+ }
}
}