Afform - Enable multiple search displays in a search form layout
authorColeman Watts <coleman@civicrm.org>
Fri, 4 Mar 2022 20:39:08 +0000 (15:39 -0500)
committerColeman Watts <coleman@civicrm.org>
Tue, 15 Mar 2022 19:58:49 +0000 (15:58 -0400)
Allows dashboard-like layouts to be composed

12 files changed:
ext/afform/admin/Civi/Api4/Action/Afform/LoadAdminData.php
ext/afform/admin/ang/afAdmin/afAdminList.controller.js
ext/afform/admin/ang/afGuiEditor.js
ext/afform/admin/ang/afGuiEditor/afGuiEditor.component.js
ext/afform/admin/ang/afGuiEditor/afGuiEditorPalette.html
ext/afform/admin/ang/afGuiEditor/afGuiSearch.html
ext/afform/admin/ang/afGuiEditor/elements/afGuiContainer-menu.html
ext/afform/admin/ang/afGuiEditor/elements/afGuiContainer.component.js
ext/afform/admin/ang/afGuiEditor/elements/afGuiContainer.html
ext/afform/admin/ang/afGuiEditor/elements/afGuiField-menu.html
ext/afform/admin/ang/afGuiEditor/elements/afGuiSearchContainer-menu.html [new file with mode: 0644]
ext/afform/admin/ang/afGuiEditor/elements/afGuiSearchDisplay.html

index aa85b05daf08ebfcd8e1f6406e7173948b47df23..9248593c76f45f7ae0e5a664102732ac2520011b 100644 (file)
@@ -184,7 +184,7 @@ class LoadAdminData extends \Civi\Api4\Generic\AbstractAction {
             ->setSavedSearch($displayTag['search-name']);
         }
         $display = $displayGet
-          ->addSelect('*', 'type:name', 'type:icon', 'saved_search_id.name', 'saved_search_id.api_entity', 'saved_search_id.api_params')
+          ->addSelect('*', 'type:name', 'type:icon', 'saved_search_id.name', 'saved_search_id.label', 'saved_search_id.api_entity', 'saved_search_id.api_params')
           ->execute()->first();
         $display['calc_fields'] = $this->getCalcFields($display['saved_search_id.api_entity'], $display['saved_search_id.api_params']);
         $display['filters'] = empty($displayTag['filters']) ? NULL : (\CRM_Utils_JS::getRawProps($displayTag['filters']) ?: NULL);
index 019281936b0404c84f1e47a448ce42b80e8b9fb5..cb25346714d6070fb0a9b7c87b9f15af8b170ca1 100644 (file)
@@ -1,7 +1,7 @@
 (function(angular, $, _) {
   "use strict";
 
-  angular.module('afAdmin').controller('afAdminList', function($scope, afforms, crmApi4, crmStatus) {
+  angular.module('afAdmin').controller('afAdminList', function($scope, afforms, crmApi4, crmStatus, afGui) {
     var ts = $scope.ts = CRM.ts('org.civicrm.afform_admin'),
       ctrl = $scope.$ctrl = this;
     this.sortField = 'title';
     }
 
     this.createLinks = function() {
-      ctrl.searchCreateLinks = '';
-      if ($scope.types[ctrl.tab].options) {
+      // Reset search input in dropdown
+      $scope.searchCreateLinks.label = '';
+      // A value means it's alredy loaded. Null means it's loading.
+      if ($scope.types[ctrl.tab].options || $scope.types[ctrl.tab].options === null) {
         return;
       }
+      $scope.types[ctrl.tab].options = null;
       var links = [];
 
       if (ctrl.tab === 'form') {
       }
 
       if (ctrl.tab === 'search') {
-        var searchNames = [];
-        // Non-aggregated query will return the same search multiple times - once per display
-        crmApi4('SavedSearch', 'get', {
-          select: ['name', 'label', 'display.name', 'display.label', 'display.type:icon'],
-          where: [['api_entity', 'IS NOT NULL'], ['api_params', 'IS NOT NULL']],
-          join: [['SearchDisplay AS display', 'LEFT', ['id', '=', 'display.saved_search_id']]],
-          orderBy: {'label':'ASC'}
-        }).then(function(searches) {
-          _.each(searches, function(search) {
-            // Add default display for each search (track searchNames in a var to just add once per search)
-            if (!_.includes(searchNames, search.name)) {
-              searchNames.push(search.name);
-              links.push({
-                url: '#create/search/' + search.name,
-                label: search.label + ': ' + ts('Search results table'),
-                icon: 'fa-table'
-              });
-            }
-            // If the search has no displays (other than the default) this will be empty
-            if (search['display.name']) {
-              links.push({
-                url: '#create/search/' + search.name + '.' + search['display.name'],
-                label: search.label + ': ' + search['display.label'],
-                icon: search['display.type:icon']
-              });
-            }
-          });
+        afGui.getAllSearchDisplays().then(function(links) {
           $scope.types.search.options = links;
         });
       }
index b21e5a4447fb89939f5c0593633df623aec81842..60391af1f2b42c90f31774e3bdf42d5e82bfe3cf 100644 (file)
           return CRM.afGuiEditor.searchDisplays[searchName + (displayName ? '.' + displayName : '')];
         },
 
+        getAllSearchDisplays: function() {
+          var links = [],
+            searchNames = [],
+            deferred = $q.defer();
+          // Non-aggregated query will return the same search multiple times - once per display
+          crmApi4('SavedSearch', 'get', {
+            select: ['name', 'label', 'display.name', 'display.label', 'display.type:name', 'display.type:icon'],
+            where: [['api_entity', 'IS NOT NULL'], ['api_params', 'IS NOT NULL']],
+            join: [['SearchDisplay AS display', 'LEFT', ['id', '=', 'display.saved_search_id']]],
+            orderBy: {'label':'ASC'}
+          }).then(function(searches) {
+            _.each(searches, function(search) {
+              // Add default display for each search (track searchNames in a var to just add once per search)
+              if (!_.includes(searchNames, search.name)) {
+                searchNames.push(search.name);
+                links.push({
+                  key: search.name,
+                  url: '#create/search/' + search.name,
+                  label: search.label + ': ' + ts('Search results table'),
+                  tag: 'crm-search-display-table',
+                  icon: 'fa-table'
+                });
+              }
+              // If the search has no displays (other than the default) this will be empty
+              if (search['display.name']) {
+                links.push({
+                  key: search.name + '.' + search['display.name'],
+                  url: '#create/search/' + search.name + '.' + search['display.name'],
+                  label: search.label + ': ' + search['display.label'],
+                  tag: search['display.type:name'],
+                  icon: search['display.type:icon']
+                });
+              }
+            });
+            deferred.resolve(links);
+          });
+          return deferred.promise;
+        },
+
         // Recursively searches a collection and its children using _.filter
         // Returns an array of all matches, or an object if the indexBy param is used
         findRecursive: function findRecursive(collection, predicate, indexBy) {
index 95e89ee2eacab14d3d98e842f3926593f1df6923..6e62c1fcccb3f36cbc5fd01b10dedf6f2b4881b9 100644 (file)
@@ -24,6 +24,7 @@
       this.afform = null;
       $scope.saving = false;
       $scope.selectedEntityName = null;
+      $scope.searchDisplayListFilter = {};
       this.meta = afGui.meta;
       var editor = this,
         sortableOptions = {};
             editor.layout['#children'].push(afGui.meta.elements.submit.element);
           }
         }
-
-        else if (editor.getFormType() === 'block') {
+        else {
           editor.layout['#children'] = editor.afform.layout;
+        }
+
+        if (editor.getFormType() === 'block') {
           editor.blockEntity = editor.afform.join_entity || editor.afform.entity_type;
           $scope.entities[editor.blockEntity] = backfillEntityDefaults({
             type: editor.blockEntity,
         }
 
         else if (editor.getFormType() === 'search') {
-          editor.layout['#children'] = afGui.findRecursive(editor.afform.layout, {'af-fieldset': ''})[0]['#children'];
-          var searchFieldsets = afGui.findRecursive(editor.afform.layout, {'af-fieldset': ''});
-          editor.searchDisplays = _.transform(searchFieldsets, function(searchDisplays, fieldset) {
-            var displayElement = afGui.findRecursive(fieldset['#children'], function(item) {
-              return item['search-name'] && item['#tag'] && item['#tag'].indexOf('crm-search-display-') === 0;
-            })[0];
-            if (displayElement) {
-              searchDisplays[displayElement['search-name'] + (displayElement['display-name'] ? '.' + displayElement['display-name'] : '')] = {
-                element: displayElement,
-                fieldset: fieldset,
-                settings: afGui.getSearchDisplay(displayElement['search-name'], displayElement['display-name'])
-              };
-            }
-          }, {});
+          editor.searchDisplays = getSearchDisplaysOnForm();
         }
 
         // Set changesSaved to true on initial load, false thereafter whenever changes are made to the model
         }
       };
 
+      // Collects all search displays currently on the form
+      function getSearchDisplaysOnForm() {
+        var searchFieldsets = afGui.findRecursive(editor.afform.layout, {'af-fieldset': ''});
+        return _.transform(searchFieldsets, function(searchDisplays, fieldset) {
+          var displayElement = afGui.findRecursive(fieldset['#children'], function(item) {
+            return item['search-name'] && item['#tag'] && item['#tag'].indexOf('crm-search-display-') === 0;
+          })[0];
+          if (displayElement) {
+            searchDisplays[displayElement['search-name'] + (displayElement['display-name'] ? '.' + displayElement['display-name'] : '')] = {
+              element: displayElement,
+              fieldset: fieldset,
+              settings: afGui.getSearchDisplay(displayElement['search-name'], displayElement['display-name'])
+            };
+          }
+        }, {});
+      }
+
+      // Load data for "Add search display" dropdown
+      this.getSearchDisplaySelector = function() {
+        // Reset search input in dropdown
+        $scope.searchDisplayListFilter.label = '';
+        // A value means it's alredy loaded. Null means it's loading.
+        if (!editor.searchOptions && editor.searchOptions !== null) {
+          editor.searchOptions = null;
+          afGui.getAllSearchDisplays().then(function(links) {
+            editor.searchOptions = links;
+          });
+        }
+      };
+
+      this.addSearchDisplay = function(display) {
+        var searchName = display.key.split('.')[0];
+        var displayName = display.key.split('.')[1] || '';
+        var fieldset = {
+          '#tag': 'div',
+          'af-fieldset': '',
+          '#children': [
+            {
+              '#tag': display.tag,
+              'search-name': searchName,
+              'display-name': displayName,
+            }
+          ]
+        };
+        var meta = {
+          fieldset: fieldset,
+          element: fieldset['#children'][0],
+          settings: afGui.getSearchDisplay(searchName, displayName),
+        };
+        editor.searchDisplays[display.key] = meta;
+
+        function addToCanvas() {
+          editor.layout['#children'].push(fieldset);
+          editor.selectEntity(display.key);
+        }
+        if (meta.settings) {
+          addToCanvas();
+        } else {
+          $timeout(editor.adjustTabWidths);
+          crmApi4('Afform', 'loadAdminData', {
+            definition: {type: 'search'},
+            entity: display.key
+          }, 0).then(function(data) {
+            afGui.addMeta(data);
+            meta.settings = afGui.getSearchDisplay(searchName, displayName);
+            addToCanvas();
+          });
+        }
+      };
+
+      // Triggered by afGuiContainer.removeElement
+      this.onRemoveElement = function() {
+        // Keep this.searchDisplays in-sync when deleteing stuff from the form
+        if (editor.getFormType() === 'search') {
+          var current = getSearchDisplaysOnForm();
+          _.each(_.keys(editor.searchDisplays), function(key) {
+            if (!(key in current)) {
+              delete editor.searchDisplays[key];
+              editor.selectEntity(null);
+            }
+          });
+        }
+      };
+
+      // This function used to be needed to build a menu of available contact_id fields
+      // but is no longer used for that and is overkill for what it does now.
       function getSearchFilterOptions(searchDisplay) {
         var
           entityCount = {},
index 1d59dce2752822d827e202e19f7a07ca02d48795..56b6eccb036ac747a32dedc6f173a42611eb9238 100644 (file)
@@ -16,7 +16,7 @@
         </a>
       </li>
       <li role="presentation" ng-repeat="(key, display) in editor.searchDisplays" class="fluid-width-tab" ng-class="{active: selectedEntityName === key}" title="{{ display.label }}">
-        <a href ng-click="editor.selectEntity(key)">
+        <a href ng-click="display.settings && editor.selectEntity(key)">
           <i ng-if="display.settings" class="crm-i {{:: display.settings['type:icon'] }}"></i>
           <i ng-if="!display.settings" class="crm-i fa-spin fa-spinner"></i>
           <span>{{ display.settings.label }}</span>
@@ -26,7 +26,7 @@
         <a href class="dropdown-toggle" data-toggle="dropdown">
           <i class="crm-i fa-plus"></i>
         </a>
-        <ul class="dropdown-menu dropdown-menu-right">
+        <ul class="dropdown-menu">
           <li ng-repeat="(entityName, entity) in editor.meta.entities" ng-if="entity.defaults">
             <a href ng-click="editor.addEntity(entityName, true)">
               <i class="crm-i {{:: entity.icon }}"></i>
           </li>
         </ul>
       </li>
+      <li role="presentation" class="dropdown" ng-if="editor.getFormType() === 'search'" title="{{:: ts('Add Search') }}">
+        <a href class="dropdown-toggle" data-toggle="dropdown" ng-click="editor.getSearchDisplaySelector();">
+          <i class="crm-i fa-plus"></i>
+        </a>
+        <ul class="dropdown-menu">
+          <li ng-class="{disabled: !editor.searchOptions || !editor.searchOptions.length}">
+            <input ng-if="editor.searchOptions && editor.searchOptions.length" type="search" class="form-control" placeholder="&#xf002" ng-model="searchDisplayListFilter.label">
+            <a href ng-if="!editor.searchOptions"><i class="crm-i fa-spinner fa-spin"></i></a>
+            <a href ng-if="editor.searchOptions && !editor.searchOptions.length">{{:: ts('None Found') }}</a>
+          </li>
+          <li ng-repeat="link in editor.searchOptions | filter:searchDisplayListFilter" class="{{:: link.class }}">
+            <a href ng-click="editor.addSearchDisplay(link)">
+              <i class="crm-i {{:: link.icon }}"></i>
+              {{:: link.label }}
+            </a>
+          </li>
+        </ul>
+      </li>
     </ul>
   </div>
   <div class="panel-body" ng-include="'~/afGuiEditor/config-form.html'" ng-if="selectedEntityName === null"></div>
index bc8a7f908c2e1a77bb440db2fb96169a138033ab..76e42ba6b8efaa37aadcfcb1ec1954dfc59439ca 100644 (file)
@@ -51,7 +51,7 @@
       </div>
       <div ng-if="blockList.length">
         <label>{{:: ts('Blocks') }}</label>
-        <div ui-sortable="$ctrl.editor.getSortableOptions()" ui-sortable-update="buildPaletteLists" ng-model="blockList">
+        <div ui-sortable="$ctrl.editor.getSortableOptions($ctrl.editor.getSelectedEntityName())" ui-sortable-update="buildPaletteLists" ng-model="blockList">
           <div ng-repeat="block in blockList" ng-class="{disabled: blockInUse(block)}">
             <div class="af-gui-palette-item">{{:: blockTitles[$index] }}</div>
           </div>
@@ -59,7 +59,7 @@
       </div>
       <div ng-if="calcFieldList.length">
         <label>{{:: ts('Calculated Fields') }}</label>
-        <div ui-sortable="$ctrl.editor.getSortableOptions()" ui-sortable-update="buildPaletteLists" ng-model="calcFieldList">
+        <div ui-sortable="$ctrl.editor.getSortableOptions($ctrl.editor.getSelectedEntityName())" ui-sortable-update="buildPaletteLists" ng-model="calcFieldList">
           <div ng-repeat="field in calcFieldList" ng-class="{disabled: $ctrl.fieldInUse(field.name)}">
             <div class="af-gui-palette-item">{{:: field.defn.label }}</div>
           </div>
@@ -68,7 +68,7 @@
       <div ng-repeat="fieldGroup in fieldList">
         <div ng-if="fieldGroup.fields.length">
           <label>{{:: fieldGroup.label }}</label>
-          <div ui-sortable="{update: buildPaletteLists, items: '&gt; div:not(.disabled)', connectWith: '[ui-sortable]', placeholder: 'af-gui-dropzone'}" ui-sortable-update="$ctrl.editor.onDrop" ng-model="fieldGroup.fields">
+          <div ui-sortable="$ctrl.editor.getSortableOptions($ctrl.editor.getSelectedEntityName())" ui-sortable-update="buildPaletteLists" ng-model="fieldGroup.fields">
             <div ng-repeat="field in fieldGroup.fields" ng-class="{disabled: $ctrl.fieldInUse(field.name)}">
               {{:: getField(fieldGroup.entityType, field.name).label }}
             </div>
index 182e092e8303b98ffc175df839dedbb8f0c7bc1a..49e85dbf73c0c6b3566b357a0b01c18c80bc6aff 100644 (file)
@@ -33,4 +33,4 @@
 <li><af-gui-menu-item-border node="$ctrl.node"></af-gui-menu-item-border></li>
 <li><af-gui-menu-item-background node="$ctrl.node"></af-gui-menu-item-background></li>
 <li role="separator" class="divider"></li>
-<li><a href ng-click="$ctrl.deleteThis()"><span class="text-danger"><i class="crm-i fa-trash"></i> {{ !block ? ts('Delete this container') : ts('Delete this block') }}</span></a></li>
+<li><a href ng-click="$ctrl.deleteThis()"><span class="text-danger"><i class="crm-i fa-trash"></i> {{ !block ? ts('Remove container') : ts('Remove block') }}</span></a></li>
index f076241ace2d5b0bb652743aae576101821fcbf1..af68e05108e90dfd7793911a0968a13db97fdad2 100644 (file)
         return entityName === ctrl.editor.getSelectedEntityName();
       };
 
+      $scope.isSelectedSearchFieldset = function(node) {
+        var key = $scope.getSearchKey(node);
+        return key === ctrl.editor.getSelectedEntityName();
+      };
+
+      $scope.getSearchKey = function(node) {
+        var searchDisplays = afGui.findRecursive(node['#children'], function(item) {
+          return item['#tag'] && item['#tag'].indexOf('crm-search-display-') === 0 && item['search-name'];
+        });
+        if (searchDisplays && searchDisplays.length) {
+          return searchDisplays[0]['search-name'] + (searchDisplays[0]['display-name'] ? '.' + searchDisplays[0]['display-name'] : '');
+        }
+      };
+
+      this.getSearchDisplay = function(node) {
+        var searchKey = $scope.getSearchKey(node);
+        if (searchKey) {
+          return afGui.getSearchDisplay.apply(null, searchKey.split('.'));
+        }
+      };
+
       $scope.selectEntity = function() {
         if (ctrl.node['af-fieldset']) {
           ctrl.editor.selectEntity(ctrl.node['af-fieldset']);
+        } else if ('af-fieldset' in ctrl.node) {
+          var searchKey = $scope.getSearchKey(ctrl.node);
+          if (searchKey) {
+            ctrl.editor.selectEntity(searchKey);
+          }
         }
       };
 
         if (node['#tag'] === 'af-field') {
           return 'field';
         }
-        if ('af-fieldset' in node) {
+        if (node['af-fieldset']) {
           return 'fieldset';
         }
+        else if ('af-fieldset' in node) {
+          return 'searchFieldset';
+        }
         if (node['af-join']) {
           return 'join';
         }
 
       this.removeElement = function(element) {
         afGui.removeRecursive($scope.getSetChildren(), {$$hashKey: element.$$hashKey});
+        ctrl.editor.onRemoveElement();
       };
 
       this.removeField = function(fieldName) {
         }
         // If entityName is not declared, this field belongs to a search
         var entityType,
+          searchDisplay = ctrl.getSearchDisplay(ctrl.node),
           prefix = _.includes(fieldName, '.') ? fieldName.split('.')[0] : null;
-        _.each(afGui.meta.searchDisplays, function(searchDisplay) {
+        if (searchDisplay) {
           if (prefix) {
             _.each(searchDisplay['saved_search_id.api_params'].join, function(join) {
               var joinInfo = join[0].split(' AS ');
           if (!entityType && fieldName && afGui.getField(searchDisplay['saved_search_id.api_entity'], fieldName)) {
             entityType = searchDisplay['saved_search_id.api_entity'];
           }
-          if (entityType) {
-            return false;
-          }
-        });
+        }
         return entityType || _.map(afGui.meta.searchDisplays, 'saved_search_id.api_entity')[0];
       };
 
index ad3fe18eb15a422d06e7052bba89cea89ff655fd..c8852476ca480695e619983fb31f4bb0ce735a8f 100644 (file)
@@ -1,6 +1,7 @@
 <div class="af-gui-bar" ng-if="$ctrl.node['#tag']" ng-click="selectEntity()" >
   <div ng-if="!$ctrl.loading" class="form-inline">
     <span ng-if="$ctrl.getNodeType($ctrl.node) == 'fieldset'">{{ $ctrl.editor.getEntity($ctrl.entityName).label }}</span>
+    <span ng-if="$ctrl.getNodeType($ctrl.node) == 'searchFieldset'">{{:: ts('Search Display') }}</span>
     <span ng-if="block">{{ $ctrl.join ? ts($ctrl.join) + ':' : ts('Block:') }}</span>
     <span ng-if="!block">{{ tags[$ctrl.node['#tag']] }}</span>
     <select ng-if="block" ng-model="block.directive" ng-change="selectBlockDirective()">
@@ -14,7 +15,7 @@
         <button type="button" class="btn btn-default btn-xs dropdown-toggle af-gui-add-element-button" data-toggle="dropdown" title="{{:: ts('Configure') }}">
           <span><i class="crm-i fa-gear"></i></span>
         </button>
-        <ul class="dropdown-menu dropdown-menu-right" ng-if="menu.open" ng-include="'~/afGuiEditor/elements/afGuiContainer-menu.html'"></ul>
+        <ul class="dropdown-menu dropdown-menu-right" ng-if="menu.open" ng-include="'~/afGuiEditor/elements/' + ($ctrl.node['af-fieldset'] === '' ? 'afGuiSearchContainer' : 'afGuiContainer') + '-menu.html'"></ul>
       </div>
     </div>
   </div>
@@ -30,6 +31,7 @@
       <af-gui-text ng-switch-when="text" node="item" delete-this="$ctrl.removeElement(item)" class="af-gui-element af-gui-text" ></af-gui-text>
       <af-gui-markup ng-switch-when="markup" node="item" delete-this="$ctrl.removeElement(item)" class="af-gui-markup" ></af-gui-markup>
       <af-gui-button ng-switch-when="button" node="item" delete-this="$ctrl.removeElement(item)" class="af-gui-element af-gui-button" ></af-gui-button>
+      <af-gui-container ng-switch-when="searchFieldset" node="item" delete-this="$ctrl.removeElement(item)" style="{{ item.style }}" class="af-gui-container af-gui-fieldset af-gui-container-type-{{ item['#tag'] }}" ng-class="{'af-entity-selected': isSelectedSearchFieldset(item)}" data-entity="{{ getSearchKey(item) }}" ></af-gui-container>
       <af-gui-search-display ng-switch-when="searchDisplay" node="item" class="af-gui-element"></af-gui-search-display>
     </div>
   </div>
index 0739242f2c6b25baa5b5a00b5da5a4b42df87769..85de8d309f5a915ef0e397608a59e5125ce622c7 100644 (file)
@@ -70,6 +70,6 @@
 <li role="separator" class="divider"></li>
 <li>
   <a href ng-click="$ctrl.deleteThis()" title="{{:: ts('Remove field from form') }}">
-    <span class="text-danger"><i class="crm-i fa-trash"></i> {{:: ts('Delete this field') }}</span>
+    <span class="text-danger"><i class="crm-i fa-trash"></i> {{:: ts('Remove field') }}</span>
   </a>
 </li>
diff --git a/ext/afform/admin/ang/afGuiEditor/elements/afGuiSearchContainer-menu.html b/ext/afform/admin/ang/afGuiEditor/elements/afGuiSearchContainer-menu.html
new file mode 100644 (file)
index 0000000..84db115
--- /dev/null
@@ -0,0 +1,12 @@
+<li ng-if="tags[$ctrl.node['#tag']]">
+  <div class="af-gui-field-select-in-dropdown form-inline" ng-click="$event.stopPropagation()">
+    {{:: ts('Element:') }}
+    <select class="form-control" ng-model="$ctrl.node['#tag']" title="{{:: ts('Container type') }}">
+      <option ng-repeat="(opt, label) in tags" value="{{ opt }}">{{ label }}</option>
+    </select>
+  </div>
+</li>
+<li><af-gui-menu-item-border node="$ctrl.node"></af-gui-menu-item-border></li>
+<li><af-gui-menu-item-background node="$ctrl.node"></af-gui-menu-item-background></li>
+<li role="separator" class="divider"></li>
+<li><a href ng-click="$ctrl.deleteThis()"><span class="text-danger"><i class="crm-i fa-trash"></i> {{:: ts('Remove display') }}</span></a></li>
index 7666a197afeb72afab9e750f3909f827397d3acd..90b5fdeede7a591e8728c1e9e1d8ca6e8c8d374d 100644 (file)
@@ -1,6 +1,6 @@
 <div class="af-gui-bar">
   <div class="form-inline">
-    <span>{{ $ctrl.display.label }}</span>
+    <span>{{:: $ctrl.display['saved_search_id.label'] }}: {{:: $ctrl.display.label }}</span>
     <div class="btn-group pull-right" af-gui-menu>
       <button type="button" class="btn btn-default btn-xs dropdown-toggle af-gui-add-element-button" data-toggle="dropdown" title="{{:: ts('Configure') }}">
         <span><i class="crm-i fa-gear"></i></span>
@@ -16,5 +16,5 @@
   </div>
 </div>
 <p class="text-center af-gui-search-display">
-  <i class="crm-i fa-3x {{ $ctrl.display['type:icon'] }}"></i>
+  <i class="crm-i fa-3x {{:: $ctrl.display['type:icon'] }}"></i>
 </p>