From 44402a2e67350bd28f3c19e6e8aa5f055f235672 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Tue, 20 Oct 2020 15:55:00 -0400 Subject: [PATCH] Search ext: Add searchDisplay and searchPage modules --- CRM/Contact/BAO/SavedSearch.php | 2 +- ext/search/CRM/Search/Page/Admin.php | 5 +- ext/search/CRM/Search/Page/Search.php | 30 ++++++ ext/search/CRM/Search/Upgrader.php | 2 +- ext/search/Civi/Search/Actions.php | 4 +- ...tions.ang.php => crmSearchActions.ang.php} | 8 +- ...s.module.js => crmSearchActions.module.js} | 2 +- .../SaveSmartGroup.ctrl.js | 4 +- .../crmSearchActionDelete.ctrl.js | 2 +- .../crmSearchActionDelete.html | 0 .../crmSearchActionUpdate.ctrl.js | 2 +- .../crmSearchActionUpdate.html | 0 .../crmSearchActions.component.js | 8 +- .../crmSearchActions.html | 0 .../saveSmartGroup.directive.js | 5 +- .../saveSmartGroup.html | 0 ext/search/ang/crmSearchDisplay.ang.php | 17 +++ ext/search/ang/crmSearchDisplay.module.js | 7 ++ .../crmSearchDisplayTable.component.js | 100 ++++++++++++++++++ .../crmSearchDisplayTable.html | 40 +++++++ ext/search/ang/crmSearchPage.ang.php | 14 +++ ext/search/ang/crmSearchPage.module.js | 31 ++++++ ext/search/ang/crmSearchPage/display.html | 7 ++ ext/search/ang/searchAdmin.ang.php | 4 +- ext/search/ang/searchAdmin.module.js | 4 +- .../ang/searchAdmin/compose/criteria.html | 4 - .../ang/searchAdmin/compose/results.html | 4 +- .../searchAdmin/crmSearchAdmin.component.js | 62 ++++++++--- .../ang/searchAdmin/crmSearchAdmin.html | 16 ++- ext/search/ang/searchAdmin/display.html | 9 ++ .../searchAdminDisplayTable.component.js | 68 ++++++++++++ .../displays/searchAdminDisplayTable.html | 33 ++++++ .../ang/searchAdmin/displays/table.html | 3 + ext/search/ang/searchAdmin/group.html | 10 +- .../ang/searchAdmin/searchList.controller.js | 13 ++- ext/search/ang/searchAdmin/searchList.html | 6 +- ext/search/ang/searchAdmin/tabs.html | 10 +- ext/search/css/search.css | 2 +- .../templates/CRM/Search/Page/Search.tpl | 0 ext/search/xml/Menu/search.xml | 7 +- 40 files changed, 482 insertions(+), 63 deletions(-) create mode 100644 ext/search/CRM/Search/Page/Search.php rename ext/search/ang/{searchActions.ang.php => crmSearchActions.ang.php} (70%) rename ext/search/ang/{searchActions.module.js => crmSearchActions.module.js} (55%) rename ext/search/ang/{searchActions => crmSearchActions}/SaveSmartGroup.ctrl.js (92%) rename ext/search/ang/{searchActions => crmSearchActions}/crmSearchActionDelete.ctrl.js (82%) rename ext/search/ang/{searchActions => crmSearchActions}/crmSearchActionDelete.html (100%) rename ext/search/ang/{searchActions => crmSearchActions}/crmSearchActionUpdate.ctrl.js (92%) rename ext/search/ang/{searchActions => crmSearchActions}/crmSearchActionUpdate.html (100%) rename ext/search/ang/{searchActions => crmSearchActions}/crmSearchActions.component.js (87%) rename ext/search/ang/{searchActions => crmSearchActions}/crmSearchActions.html (100%) rename ext/search/ang/{searchActions => crmSearchActions}/saveSmartGroup.directive.js (84%) rename ext/search/ang/{searchActions => crmSearchActions}/saveSmartGroup.html (100%) create mode 100644 ext/search/ang/crmSearchDisplay.ang.php create mode 100644 ext/search/ang/crmSearchDisplay.module.js create mode 100644 ext/search/ang/crmSearchDisplay/crmSearchDisplayTable.component.js create mode 100644 ext/search/ang/crmSearchDisplay/crmSearchDisplayTable.html create mode 100644 ext/search/ang/crmSearchPage.ang.php create mode 100644 ext/search/ang/crmSearchPage.module.js create mode 100644 ext/search/ang/crmSearchPage/display.html create mode 100644 ext/search/ang/searchAdmin/display.html create mode 100644 ext/search/ang/searchAdmin/displays/searchAdminDisplayTable.component.js create mode 100644 ext/search/ang/searchAdmin/displays/searchAdminDisplayTable.html create mode 100644 ext/search/ang/searchAdmin/displays/table.html create mode 100644 ext/search/templates/CRM/Search/Page/Search.tpl diff --git a/CRM/Contact/BAO/SavedSearch.php b/CRM/Contact/BAO/SavedSearch.php index 0b5881513b..d5e4456720 100644 --- a/CRM/Contact/BAO/SavedSearch.php +++ b/CRM/Contact/BAO/SavedSearch.php @@ -443,7 +443,7 @@ LEFT JOIN civicrm_email ON (contact_a.id = civicrm_email.contact_id AND civicrm_ $savedSearch = self::retrieve(['id' => $id]); // APIv4 search if (!empty($savedSearch->api_entity)) { - return CRM_Utils_System::url('civicrm/search', NULL, FALSE, "/edit/$id"); + return CRM_Utils_System::url('civicrm/admin/search', NULL, FALSE, "/edit/$id"); } // Classic search builder if (!empty($savedSearch->mapping_id)) { diff --git a/ext/search/CRM/Search/Page/Admin.php b/ext/search/CRM/Search/Page/Admin.php index e98f208a15..ccd4cf6b24 100644 --- a/ext/search/CRM/Search/Page/Admin.php +++ b/ext/search/CRM/Search/Page/Admin.php @@ -17,7 +17,7 @@ class CRM_Search_Page_Admin extends CRM_Core_Page { public function run() { $breadCrumb = [ 'title' => ts('Search Kit'), - 'url' => CRM_Utils_System::url('civicrm/search', NULL, FALSE, '/list'), + 'url' => CRM_Utils_System::url('civicrm/admin/search', NULL, FALSE, '/list'), ]; CRM_Utils_System::appendBreadCrumb([$breadCrumb]); @@ -40,8 +40,7 @@ class CRM_Search_Page_Admin extends CRM_Core_Page { // Load angular module $loader = new Civi\Angular\AngularLoader(); - $loader->setModules(['searchAdmin']); - $loader->setPageName('civicrm/search'); + $loader->setPageName('civicrm/admin/search'); $loader->useApp([ 'defaultRoute' => '/list', ]); diff --git a/ext/search/CRM/Search/Page/Search.php b/ext/search/CRM/Search/Page/Search.php new file mode 100644 index 0000000000..053a8eeacb --- /dev/null +++ b/ext/search/CRM/Search/Page/Search.php @@ -0,0 +1,30 @@ +addBundle('bootstrap3'); + + // Load angular module + $loader = new Civi\Angular\AngularLoader(); + $loader->setPageName('civicrm/search'); + $loader->useApp(); + $loader->load(); + + parent::run(); + } + +} diff --git a/ext/search/CRM/Search/Upgrader.php b/ext/search/CRM/Search/Upgrader.php index 667460ecba..87dfb5f03d 100644 --- a/ext/search/CRM/Search/Upgrader.php +++ b/ext/search/CRM/Search/Upgrader.php @@ -14,7 +14,7 @@ class CRM_Search_Upgrader extends CRM_Search_Upgrader_Base { ->addValue('parent_id:name', 'Search') ->addValue('label', E::ts('Search Kit')) ->addValue('name', 'search_kit') - ->addValue('url', 'civicrm/search') + ->addValue('url', 'civicrm/admin/search') ->addValue('icon', 'crm-i fa-search-plus') ->addValue('has_separator', 2) ->addValue('weight', 99) diff --git a/ext/search/Civi/Search/Actions.php b/ext/search/Civi/Search/Actions.php index 4771101183..5918d757dd 100644 --- a/ext/search/Civi/Search/Actions.php +++ b/ext/search/Civi/Search/Actions.php @@ -58,13 +58,13 @@ class Actions { 'title' => ts('Update %1'), 'icon' => 'fa-save', 'entities' => [], - 'uiDialog' => ['templateUrl' => '~/searchActions/crmSearchActionUpdate.html'], + 'uiDialog' => ['templateUrl' => '~/crmSearchActions/crmSearchActionUpdate.html'], ], 'delete' => [ 'title' => ts('Delete %1'), 'icon' => 'fa-trash', 'entities' => [], - 'uiDialog' => ['templateUrl' => '~/searchActions/crmSearchActionDelete.html'], + 'uiDialog' => ['templateUrl' => '~/crmSearchActions/crmSearchActionDelete.html'], ], ]; diff --git a/ext/search/ang/searchActions.ang.php b/ext/search/ang/crmSearchActions.ang.php similarity index 70% rename from ext/search/ang/searchActions.ang.php rename to ext/search/ang/crmSearchActions.ang.php index 6699110540..4b0841c470 100644 --- a/ext/search/ang/searchActions.ang.php +++ b/ext/search/ang/crmSearchActions.ang.php @@ -2,12 +2,12 @@ // Autoloader data for search actions. return [ 'js' => [ - 'ang/searchActions.module.js', - 'ang/searchActions/*.js', - 'ang/searchActions/*/*.js', + 'ang/crmSearchActions.module.js', + 'ang/crmSearchActions/*.js', + 'ang/crmSearchActions/*/*.js', ], 'partials' => [ - 'ang/searchActions', + 'ang/crmSearchActions', ], 'basePages' => [], 'requires' => ['crmUi', 'crmUtil', 'dialogService', 'api4'], diff --git a/ext/search/ang/searchActions.module.js b/ext/search/ang/crmSearchActions.module.js similarity index 55% rename from ext/search/ang/searchActions.module.js rename to ext/search/ang/crmSearchActions.module.js index 9f4204c0bf..912d2e54ff 100644 --- a/ext/search/ang/searchActions.module.js +++ b/ext/search/ang/crmSearchActions.module.js @@ -2,6 +2,6 @@ "use strict"; // Declare module - angular.module('searchActions', CRM.angRequires('searchActions')); + angular.module('crmSearchActions', CRM.angRequires('crmSearchActions')); })(angular, CRM.$, CRM._); diff --git a/ext/search/ang/searchActions/SaveSmartGroup.ctrl.js b/ext/search/ang/crmSearchActions/SaveSmartGroup.ctrl.js similarity index 92% rename from ext/search/ang/searchActions/SaveSmartGroup.ctrl.js rename to ext/search/ang/crmSearchActions/SaveSmartGroup.ctrl.js index b7d8c1f1d0..7b89a9dc28 100644 --- a/ext/search/ang/searchActions/SaveSmartGroup.ctrl.js +++ b/ext/search/ang/crmSearchActions/SaveSmartGroup.ctrl.js @@ -1,7 +1,7 @@ (function(angular, $, _) { "use strict"; - angular.module('searchActions').controller('SaveSmartGroup', function ($scope, $element, $timeout, crmApi4, dialogService, searchMeta) { + angular.module('crmSearchActions').controller('SaveSmartGroup', function ($scope, $element, $timeout, crmApi4, dialogService, searchMeta) { var ts = $scope.ts = CRM.ts(), model = $scope.model; $scope.groupEntityRefParams = { @@ -35,7 +35,7 @@ $scope.perm = { administerReservedGroups: CRM.checkPerm('administer reserved groups') }; - $scope.groupOptions = CRM.searchActions.groupOptions; + $scope.groupOptions = CRM.crmSearchActions.groupOptions; $element.on('change', '#api-save-search-select-group', function() { if ($(this).val()) { $scope.$apply(function() { diff --git a/ext/search/ang/searchActions/crmSearchActionDelete.ctrl.js b/ext/search/ang/crmSearchActions/crmSearchActionDelete.ctrl.js similarity index 82% rename from ext/search/ang/searchActions/crmSearchActionDelete.ctrl.js rename to ext/search/ang/crmSearchActions/crmSearchActionDelete.ctrl.js index 4f0cf17a65..b842e96a39 100644 --- a/ext/search/ang/searchActions/crmSearchActionDelete.ctrl.js +++ b/ext/search/ang/crmSearchActions/crmSearchActionDelete.ctrl.js @@ -1,7 +1,7 @@ (function(angular, $, _) { "use strict"; - angular.module('searchActions').controller('crmSearchActionDelete', function($scope, crmApi4, dialogService, searchMeta) { + angular.module('crmSearchActions').controller('crmSearchActionDelete', function($scope, crmApi4, dialogService, searchMeta) { var ts = $scope.ts = CRM.ts(), model = $scope.model, ctrl = $scope.$ctrl = this; diff --git a/ext/search/ang/searchActions/crmSearchActionDelete.html b/ext/search/ang/crmSearchActions/crmSearchActionDelete.html similarity index 100% rename from ext/search/ang/searchActions/crmSearchActionDelete.html rename to ext/search/ang/crmSearchActions/crmSearchActionDelete.html diff --git a/ext/search/ang/searchActions/crmSearchActionUpdate.ctrl.js b/ext/search/ang/crmSearchActions/crmSearchActionUpdate.ctrl.js similarity index 92% rename from ext/search/ang/searchActions/crmSearchActionUpdate.ctrl.js rename to ext/search/ang/crmSearchActions/crmSearchActionUpdate.ctrl.js index 18bd6fd082..229d75c735 100644 --- a/ext/search/ang/searchActions/crmSearchActionUpdate.ctrl.js +++ b/ext/search/ang/crmSearchActions/crmSearchActionUpdate.ctrl.js @@ -1,7 +1,7 @@ (function(angular, $, _) { "use strict"; - angular.module('searchActions').controller('crmSearchActionUpdate', function ($scope, $timeout, crmApi4, dialogService, searchMeta) { + angular.module('crmSearchActions').controller('crmSearchActionUpdate', function ($scope, $timeout, crmApi4, dialogService, searchMeta) { var ts = $scope.ts = CRM.ts(), model = $scope.model, ctrl = $scope.$ctrl = this; diff --git a/ext/search/ang/searchActions/crmSearchActionUpdate.html b/ext/search/ang/crmSearchActions/crmSearchActionUpdate.html similarity index 100% rename from ext/search/ang/searchActions/crmSearchActionUpdate.html rename to ext/search/ang/crmSearchActions/crmSearchActionUpdate.html diff --git a/ext/search/ang/searchActions/crmSearchActions.component.js b/ext/search/ang/crmSearchActions/crmSearchActions.component.js similarity index 87% rename from ext/search/ang/searchActions/crmSearchActions.component.js rename to ext/search/ang/crmSearchActions/crmSearchActions.component.js index 2638d5c2bb..1958e5da63 100644 --- a/ext/search/ang/searchActions/crmSearchActions.component.js +++ b/ext/search/ang/crmSearchActions/crmSearchActions.component.js @@ -1,13 +1,13 @@ (function(angular, $, _) { "use strict"; - angular.module('searchActions').component('crmSearchActions', { + angular.module('crmSearchActions').component('crmSearchActions', { bindings: { entity: '<', refresh: '&', ids: '<' }, - templateUrl: '~/searchActions/crmSearchActions.html', + templateUrl: '~/crmSearchActions/crmSearchActions.html', controller: function($scope, crmApi4, dialogService, searchMeta) { var ts = $scope.ts = CRM.ts(), ctrl = this, @@ -28,9 +28,9 @@ where: [['name', 'IN', ['update', 'delete']]], }, ['name']).then(function(allowed) { _.each(allowed, function(action) { - CRM.searchActions.tasks[action].entities.push(ctrl.entity); + CRM.crmSearchActions.tasks[action].entities.push(ctrl.entity); }); - var actions = _.transform(_.cloneDeep(CRM.searchActions.tasks), function(actions, action) { + var actions = _.transform(_.cloneDeep(CRM.crmSearchActions.tasks), function(actions, action) { if (_.includes(action.entities, ctrl.entity)) { action.title = action.title.replace('%1', entityTitle); actions.push(action); diff --git a/ext/search/ang/searchActions/crmSearchActions.html b/ext/search/ang/crmSearchActions/crmSearchActions.html similarity index 100% rename from ext/search/ang/searchActions/crmSearchActions.html rename to ext/search/ang/crmSearchActions/crmSearchActions.html diff --git a/ext/search/ang/searchActions/saveSmartGroup.directive.js b/ext/search/ang/crmSearchActions/saveSmartGroup.directive.js similarity index 84% rename from ext/search/ang/searchActions/saveSmartGroup.directive.js rename to ext/search/ang/crmSearchActions/saveSmartGroup.directive.js index 9f59a7d668..bd68699d85 100644 --- a/ext/search/ang/searchActions/saveSmartGroup.directive.js +++ b/ext/search/ang/crmSearchActions/saveSmartGroup.directive.js @@ -1,13 +1,14 @@ (function(angular, $, _) { "use strict"; - angular.module('searchActions').directive('saveSmartGroup', function() { + angular.module('crmSearchActions').directive('saveSmartGroup', function() { return { bindToController: { load: '<', entity: '<', params: '<' }, + restrict: 'A', controller: function ($scope, $element, dialogService) { var ts = $scope.ts = CRM.ts(), ctrl = this; @@ -30,7 +31,7 @@ autoOpen: false, title: ts('Save smart group') }); - dialogService.open('saveSearchDialog', '~/searchActions/saveSmartGroup.html', model, options); + dialogService.open('saveSearchDialog', '~/crmSearchActions/saveSmartGroup.html', model, options); }; } }; diff --git a/ext/search/ang/searchActions/saveSmartGroup.html b/ext/search/ang/crmSearchActions/saveSmartGroup.html similarity index 100% rename from ext/search/ang/searchActions/saveSmartGroup.html rename to ext/search/ang/crmSearchActions/saveSmartGroup.html diff --git a/ext/search/ang/crmSearchDisplay.ang.php b/ext/search/ang/crmSearchDisplay.ang.php new file mode 100644 index 0000000000..a2524b3396 --- /dev/null +++ b/ext/search/ang/crmSearchDisplay.ang.php @@ -0,0 +1,17 @@ + [ + 'ang/crmSearchDisplay.module.js', + 'ang/crmSearchDisplay/*.js', + 'ang/crmSearchDisplay/*/*.js', + ], + 'partials' => [ + 'ang/crmSearchDisplay', + ], + 'basePages' => [], + 'requires' => ['crmUi', 'api4', 'crmSearchActions', 'ui.bootstrap'], + 'exports' => [ + 'crm-search-display-table' => 'E', + ], +]; diff --git a/ext/search/ang/crmSearchDisplay.module.js b/ext/search/ang/crmSearchDisplay.module.js new file mode 100644 index 0000000000..beae3e52e1 --- /dev/null +++ b/ext/search/ang/crmSearchDisplay.module.js @@ -0,0 +1,7 @@ +(function(angular, $, _) { + "use strict"; + + // Declare module + angular.module('crmSearchDisplay', CRM.angRequires('crmSearchDisplay')); + +})(angular, CRM.$, CRM._); diff --git a/ext/search/ang/crmSearchDisplay/crmSearchDisplayTable.component.js b/ext/search/ang/crmSearchDisplay/crmSearchDisplayTable.component.js new file mode 100644 index 0000000000..fe3de2165a --- /dev/null +++ b/ext/search/ang/crmSearchDisplay/crmSearchDisplayTable.component.js @@ -0,0 +1,100 @@ +(function(angular, $, _) { + "use strict"; + + angular.module('crmSearchDisplay').component('crmSearchDisplayTable', { + bindings: { + apiEntity: '<', + apiParams: '<', + settings: '<' + }, + templateUrl: '~/crmSearchDisplay/crmSearchDisplayTable.html', + controller: function($scope, crmApi4) { + var ts = $scope.ts = CRM.ts(), + ctrl = this; + + this.page = 1; + + this.$onInit = function() { + this.orderBy = this.apiParams.orderBy || {}; + this.limit = parseInt(ctrl.settings.limit || 0, 10); + _.each(ctrl.settings.columns, function(col, num) { + var index = ctrl.apiParams.select.indexOf(col.expr); + if (_.includes(col.expr, '(') && !_.includes(col.expr, ' AS ')) { + col.expr += ' AS column_' + num; + ctrl.apiParams.select[index] += ' AS column_' + num; + } + col.key = _.last(col.expr.split(' AS ')); + }); + getResults(); + }; + + function getResults() { + var params = _.merge(_.cloneDeep(ctrl.apiParams), {limit: ctrl.limit, offset: (ctrl.page - 1) * ctrl.limit, orderBy: ctrl.orderBy}); + if (ctrl.settings.pager) { + params.select.push('row_count'); + } + crmApi4(ctrl.apiEntity, 'get', params).then(function(results) { + ctrl.results = results; + ctrl.rowCount = results.count; + }); + } + + this.changePage = function() { + getResults(); + }; + + /** + * Returns crm-i icon class for a sortable column + * @param col + * @returns {string} + */ + $scope.getOrderBy = function(col) { + var dir = ctrl.orderBy && ctrl.orderBy[col.key]; + if (dir) { + return 'fa-sort-' + dir.toLowerCase(); + } + return 'fa-sort disabled'; + }; + + /** + * Called when clicking on a column header + * @param col + * @param $event + */ + $scope.setOrderBy = function(col, $event) { + var dir = $scope.getOrderBy(col) === 'fa-sort-asc' ? 'DESC' : 'ASC'; + if (!$event.shiftKey) { + ctrl.orderBy = {}; + } + ctrl.orderBy[col.key] = dir; + getResults(); + }; + + $scope.formatResult = function(row, col) { + var value = row[col.key]; + return formatFieldValue(col, value); + }; + + function formatFieldValue(col, value) { + var type = col.dataType; + if (_.isArray(value)) { + return _.map(value, function(val) { + return formatFieldValue(col, val); + }).join(', '); + } + if (value && (type === 'Date' || type === 'Timestamp') && /^\d{4}-\d{2}-\d{2}/.test(value)) { + return CRM.utils.formatDate(value, null, type === 'Timestamp'); + } + else if (type === 'Boolean' && typeof value === 'boolean') { + return value ? ts('Yes') : ts('No'); + } + else if (type === 'Money' && typeof value === 'number') { + return CRM.formatMoney(value); + } + return value; + } + + } + }); + +})(angular, CRM.$, CRM._); diff --git a/ext/search/ang/crmSearchDisplay/crmSearchDisplayTable.html b/ext/search/ang/crmSearchDisplay/crmSearchDisplayTable.html new file mode 100644 index 0000000000..a980eb5e1e --- /dev/null +++ b/ext/search/ang/crmSearchDisplay/crmSearchDisplayTable.html @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + +
+ + + + {{ col.label }} +
+ + + {{ formatResult(row, col) }} +
+
+ +
diff --git a/ext/search/ang/crmSearchPage.ang.php b/ext/search/ang/crmSearchPage.ang.php new file mode 100644 index 0000000000..54442bdcd0 --- /dev/null +++ b/ext/search/ang/crmSearchPage.ang.php @@ -0,0 +1,14 @@ + [ + 'ang/crmSearchPage.module.js', + 'ang/crmSearchPage/*.js', + 'ang/crmSearchPage/*/*.js', + ], + 'partials' => [ + 'ang/crmSearchPage', + ], + 'basePages' => ['civicrm/search'], + 'requires' => ['ngRoute', 'api4', 'crmUi', 'crmSearchDisplay'], +]; diff --git a/ext/search/ang/crmSearchPage.module.js b/ext/search/ang/crmSearchPage.module.js new file mode 100644 index 0000000000..0c8862c869 --- /dev/null +++ b/ext/search/ang/crmSearchPage.module.js @@ -0,0 +1,31 @@ +(function(angular, $, _) { + "use strict"; + + // Declare module + angular.module('crmSearchPage', CRM.angRequires('crmSearchPage')) + + + .config(function($routeProvider) { + $routeProvider.when('/display/:savedSearchName/:displayName', { + controller: 'crmSearchPageDisplay', + templateUrl: '~/crmSearchPage/display.html', + resolve: { + // Load saved search display + display: function($route, crmApi4) { + var params = $route.current.params; + return crmApi4('SearchDisplay', 'get', { + where: [['name', '=', params.displayName], ['saved_search.name', '=', params.savedSearchName]], + select: ['*', 'saved_search.api_entity', 'saved_search.api_params'] + }, 0); + } + } + }); + }) + + // Controller for displaying a search + .controller('crmSearchPageDisplay', function($scope, $routeParams, $location, display) { + this.display = display; + $scope.$ctrl = this; + }); + +})(angular, CRM.$, CRM._); diff --git a/ext/search/ang/crmSearchPage/display.html b/ext/search/ang/crmSearchPage/display.html new file mode 100644 index 0000000000..789963a44c --- /dev/null +++ b/ext/search/ang/crmSearchPage/display.html @@ -0,0 +1,7 @@ +

{{:: $ctrl.display.label }}

+ +
+
+ +
+
diff --git a/ext/search/ang/searchAdmin.ang.php b/ext/search/ang/searchAdmin.ang.php index 31a85141bf..4b944adcd6 100644 --- a/ext/search/ang/searchAdmin.ang.php +++ b/ext/search/ang/searchAdmin.ang.php @@ -12,7 +12,7 @@ return [ 'partials' => [ 'ang/searchAdmin', ], - 'basePages' => [], - 'requires' => ['crmUi', 'crmUtil', 'ngRoute', 'ui.sortable', 'ui.bootstrap', 'api4', 'searchActions'], + 'basePages' => ['civicrm/admin/search'], + 'requires' => ['crmUi', 'crmUtil', 'ngRoute', 'ui.sortable', 'ui.bootstrap', 'api4', 'crmSearchDisplay', 'crmSearchActions'], 'settingsFactory' => ['\Civi\Search\Admin', 'getAdminSettings'], ]; diff --git a/ext/search/ang/searchAdmin.module.js b/ext/search/ang/searchAdmin.module.js index 486298bca6..168aba26b4 100644 --- a/ext/search/ang/searchAdmin.module.js +++ b/ext/search/ang/searchAdmin.module.js @@ -16,7 +16,7 @@ // Load data for lists savedSearches: function(crmApi4) { return crmApi4('SavedSearch', 'get', { - select: ['id', 'api_entity', 'form_values', 'COUNT(search_display.id) AS displays', 'GROUP_CONCAT(group.title) AS groups'], + select: ['id', 'label', 'api_entity', 'form_values', 'COUNT(search_display.id) AS displays', 'GROUP_CONCAT(group.title) AS groups'], join: [['SearchDisplay AS search_display'], ['Group AS group']], where: [['api_entity', 'IS NOT NULL']], groupBy: ['id'] @@ -38,7 +38,7 @@ return crmApi4('SavedSearch', 'get', { where: [['id', '=', params.id]], chain: { - group: ['Group', 'get', {where: [['saved_search_id', '=', '$id']]}, 0], + groups: ['Group', 'get', {where: [['saved_search_id', '=', '$id']]}], displays: ['SearchDisplay', 'get', {where: [['saved_search_id', '=', '$id']]}] } }, 0); diff --git a/ext/search/ang/searchAdmin/compose/criteria.html b/ext/search/ang/searchAdmin/compose/criteria.html index 1cfa81ba70..6b5eb4b3af 100644 --- a/ext/search/ang/searchAdmin/compose/criteria.html +++ b/ext/search/ang/searchAdmin/compose/criteria.html @@ -1,9 +1,5 @@
-
- - -
diff --git a/ext/search/ang/searchAdmin/compose/results.html b/ext/search/ang/searchAdmin/compose/results.html index 9bd8f25686..049d037bb4 100644 --- a/ext/search/ang/searchAdmin/compose/results.html +++ b/ext/search/ang/searchAdmin/compose/results.html @@ -4,9 +4,9 @@ - + - {{ $ctrl.getFieldLabel(col) }} + {{ $ctrl.getFieldLabel(col) }} diff --git a/ext/search/ang/searchAdmin/crmSearchAdmin.component.js b/ext/search/ang/searchAdmin/crmSearchAdmin.component.js index 57867876d3..0e98c31d9d 100644 --- a/ext/search/ang/searchAdmin/crmSearchAdmin.component.js +++ b/ext/search/ang/searchAdmin/crmSearchAdmin.component.js @@ -26,7 +26,7 @@ $scope.controls = {tab: 'compose'}; $scope.joinTypes = [{k: false, v: ts('Optional')}, {k: true, v: ts('Required')}]; - $scope.groupOptions = CRM.searchActions.groupOptions; + $scope.groupOptions = CRM.crmSearchActions.groupOptions; $scope.entities = formatForSelect2(CRM.vars.search.schema, 'name', 'title_plural', ['description', 'icon']); this.perm = { editGroups: CRM.checkPerm('edit groups') @@ -36,15 +36,12 @@ this.entityTitle = searchMeta.getEntity(this.savedSearch.api_entity).title_plural; this.savedSearch.displays = this.savedSearch.displays || []; - this.groupExists = !!this.savedSearch.group; - - this.original = _.indexBy(_.cloneDeep(this.savedSearch.displays), 'id'); - if (this.savedSearch.group) { - this.original.group = _.cloneDeep(this.savedSearch.group); - } + this.savedSearch.groups = this.savedSearch.groups || []; + this.groupExists = !!this.savedSearch.groups.length; if (!this.savedSearch.api_params) { this.savedSearch.api_params = { + version: 4, select: getDefaultSelect(), orderBy: {}, where: [], @@ -70,16 +67,55 @@ $scope.$watch('$ctrl.savedSearch.api_params.having', onChangeFilters, true); } + $scope.$watch('$ctrl.savedSearch', onChangeAnything, true); + + // Is this savedSearch record saved, unsaved or saving + $scope.status = this.savedSearch && this.savedSearch.id ? 'saved' : 'unsaved'; + loadFieldOptions(); }; + function onChangeAnything() { + $scope.status = 'unsaved'; + } + + this.save = function() { + $scope.status = 'saving'; + var params = _.cloneDeep(ctrl.savedSearch), + apiCalls = {}, + chain = {}; + if (ctrl.groupExists) { + chain.groups = ['Group', 'save', {defaults: {saved_search_id: '$id'}, records: params.groups}]; + delete params.groups; + } else if (params.id) { + apiCalls.deleteGroup = ['Group', 'delete', {where: [['saved_search_id', '=', params.id]]}]; + } + if (params.displays && params.displays.length) { + chain.displays = ['SearchDisplay', 'replace', {where: [['saved_search_id', '=', '$id']], records: params.displays}]; + } else if (params.id) { + apiCalls.deleteDisplays = ['SearchDisplay', 'delete', {where: [['saved_search_id', '=', params.id]]}]; + } + delete params.displays; + apiCalls.saved = ['SavedSearch', 'save', {records: [params], chain: chain}, 0]; + crmApi4(apiCalls).then(function(results) { + ctrl.savedSearch.id = results.saved.id; + ctrl.savedSearch.groups = results.saved.groups || []; + ctrl.savedSearch.displays = results.saved.displays || []; + if ($scope.status === 'saving') { + $scope.status = 'saved'; + } + }); + }; + this.paramExists = function(param) { return _.includes(searchMeta.getEntity(ctrl.savedSearch.api_entity).params, param); }; this.addDisplay = function(type) { ctrl.savedSearch.displays.push({ - type: type + type: type, + label: '', + settings: {} }); $scope.selectTab('display_' + (ctrl.savedSearch.displays.length - 1)); }; @@ -95,12 +131,12 @@ }; this.addGroup = function() { - ctrl.savedSearch.group = { + ctrl.savedSearch.groups.push({ title: '', description: '', visibility: 'User and User Admin Only', group_type: [] - }; + }); ctrl.groupExists = true; $scope.selectTab('group'); }; @@ -119,8 +155,8 @@ this.removeGroup = function() { ctrl.groupExists = !ctrl.groupExists; - if (!ctrl.groupExists && (!ctrl.savedSearch.group || !ctrl.savedSearch.group.id)) { - ctrl.savedSearch.group = null; + if (!ctrl.groupExists && (!ctrl.savedSearch.groups.length || !ctrl.savedSearch.groups[0].id)) { + ctrl.savedSearch.groups.length = 0; } $scope.selectTab('compose'); }; @@ -494,7 +530,7 @@ $scope.sortableColumnOptions = { axis: 'x', - handle: '.crm-sortable', + handle: '.crm-draggable', update: function(e, ui) { // Don't allow items to be moved to position 0 if locked if (!ui.item.sortable.dropindex && ctrl.groupExists) { diff --git a/ext/search/ang/searchAdmin/crmSearchAdmin.html b/ext/search/ang/searchAdmin/crmSearchAdmin.html index b8fcb1820b..a4b184b6ec 100644 --- a/ext/search/ang/searchAdmin/crmSearchAdmin.html +++ b/ext/search/ang/searchAdmin/crmSearchAdmin.html @@ -13,9 +13,16 @@
@@ -33,6 +40,9 @@
+
+
+
diff --git a/ext/search/ang/searchAdmin/display.html b/ext/search/ang/searchAdmin/display.html new file mode 100644 index 0000000000..7e954ed8e9 --- /dev/null +++ b/ext/search/ang/searchAdmin/display.html @@ -0,0 +1,9 @@ +
+
+ + + +
+
+ +
diff --git a/ext/search/ang/searchAdmin/displays/searchAdminDisplayTable.component.js b/ext/search/ang/searchAdmin/displays/searchAdminDisplayTable.component.js new file mode 100644 index 0000000000..5d3b6b97b0 --- /dev/null +++ b/ext/search/ang/searchAdmin/displays/searchAdminDisplayTable.component.js @@ -0,0 +1,68 @@ +(function(angular, $, _) { + "use strict"; + + angular.module('searchAdmin').component('searchAdminDisplayTable', { + bindings: { + display: '<', + apiEntity: '<', + apiParams: '<' + }, + require: { + crmSearchAdmin: '^crmSearchAdmin' + }, + templateUrl: '~/searchAdmin/displays/searchAdminDisplayTable.html', + controller: function($scope, searchMeta) { + var ts = $scope.ts = CRM.ts(), + ctrl = this; + + function fieldToColumn(fieldExpr) { + var info = searchMeta.parseExpr(fieldExpr); + return { + expr: fieldExpr, + label: ctrl.getFieldLabel(fieldExpr), + dataType: (info.fn && info.fn.name === 'COUNT') ? 'Integer' : info.field.data_type + }; + } + + this.sortableOptions = { + connectWith: '.crm-search-admin-table-columns', + containment: '.crm-search-admin-table-columns-wrapper' + }; + + this.removeCol = function(index) { + ctrl.hiddenColumns.push(ctrl.display.settings.columns[index]); + ctrl.display.settings.columns.splice(index, 1); + }; + + this.restoreCol = function(index) { + ctrl.display.settings.columns.push(ctrl.hiddenColumns[index]); + ctrl.hiddenColumns.splice(index, 1); + }; + + this.$onInit = function () { + ctrl.display.settings.limit = parseInt(ctrl.display.settings.limit || 0, 10); + ctrl.getFieldLabel = ctrl.crmSearchAdmin.getFieldLabel; + if (!ctrl.display.settings.columns) { + ctrl.display.settings.columns = _.transform(ctrl.apiParams.select, function(columns, fieldExpr) { + columns.push(fieldToColumn(fieldExpr)); + }); + ctrl.hiddenColumns = []; + } else { + var activeColumns = _.collect(ctrl.display.settings.columns, 'expr'); + ctrl.hiddenColumns = _.transform(ctrl.apiParams.select, function(hiddenColumns, fieldExpr) { + if (!_.includes(activeColumns, fieldExpr)) { + hiddenColumns.push(fieldToColumn(fieldExpr)); + } + }); + _.each(activeColumns, function(fieldExpr, index) { + if (!_.includes(ctrl.apiParams.select, fieldExpr)) { + ctrl.display.settings.columns.splice(index, 1); + } + }); + } + }; + + } + }); + +})(angular, CRM.$, CRM._); diff --git a/ext/search/ang/searchAdmin/displays/searchAdminDisplayTable.html b/ext/search/ang/searchAdmin/displays/searchAdminDisplayTable.html new file mode 100644 index 0000000000..4367c4c6c7 --- /dev/null +++ b/ext/search/ang/searchAdmin/displays/searchAdminDisplayTable.html @@ -0,0 +1,33 @@ +
+
+ + + +
+
+
+
+ {{:: ts('Columns') }} +
+ {{ $ctrl.getFieldLabel(col.expr) }} +
+ + +
+
+
+
+ {{:: ts('Hidden Columns') }} +
+ {{ $ctrl.getFieldLabel(col.expr) }} +
+ + +
+
+
+
diff --git a/ext/search/ang/searchAdmin/displays/table.html b/ext/search/ang/searchAdmin/displays/table.html new file mode 100644 index 0000000000..fb5bf38723 --- /dev/null +++ b/ext/search/ang/searchAdmin/displays/table.html @@ -0,0 +1,3 @@ + +
+ diff --git a/ext/search/ang/searchAdmin/group.html b/ext/search/ang/searchAdmin/group.html index 6a4674f2b7..8eb14919c3 100644 --- a/ext/search/ang/searchAdmin/group.html +++ b/ext/search/ang/searchAdmin/group.html @@ -1,13 +1,13 @@
- {{:: ts('Smart group "%1" will be deleted.', {1: $ctrl.original.group.title}) }} + {{:: ts('Smart group "%1" will be deleted.', {1: $ctrl.savedSearch.groups[0].title}) }}
{{:: ts('Unable to create smart group because this search does not include any contacts.') }}
- +
@@ -15,19 +15,19 @@
- +
   
- +
diff --git a/ext/search/ang/searchAdmin/searchList.controller.js b/ext/search/ang/searchAdmin/searchList.controller.js index 6372ae2322..56cc6c3d67 100644 --- a/ext/search/ang/searchAdmin/searchList.controller.js +++ b/ext/search/ang/searchAdmin/searchList.controller.js @@ -1,13 +1,24 @@ (function(angular, $, _) { "use strict"; - angular.module('searchAdmin').controller('searchList', function($scope, savedSearches) { + angular.module('searchAdmin').controller('searchList', function($scope, savedSearches, crmApi4) { var ts = $scope.ts = CRM.ts(), ctrl = $scope.$ctrl = this; this.savedSearches = savedSearches; this.entityTitles = _.transform(CRM.vars.search.schema, function(titles, entity) { titles[entity.name] = entity.titlePlural; }, {}); + + this.deleteSearch = function(search) { + var index = _.findIndex(savedSearches, {id: search.id}); + if (index > -1) { + crmApi4([ + ['Group', 'delete', {where: [['saved_search_id', '=', search.id]]}], + ['SavedSearch', 'delete', {where: [['id', '=', search.id]]}] + ]); + savedSearches.splice(index, 1); + } + }; }); })(angular, CRM.$, CRM._); diff --git a/ext/search/ang/searchAdmin/searchList.html b/ext/search/ang/searchAdmin/searchList.html index e64daef51a..3f246f0c54 100644 --- a/ext/search/ang/searchAdmin/searchList.html +++ b/ext/search/ang/searchAdmin/searchList.html @@ -10,6 +10,7 @@ {{:: ts('ID') }} + {{:: ts('Label') }} {{:: ts('For') }} {{:: ts('Displays') }} {{:: ts('Smart Group') }} @@ -19,18 +20,19 @@ {{ search.id }} + {{ search.label }} {{ $ctrl.entityTitles[search.api_entity] }} {{ search.displays }} {{ search.groups.join(', ') }} - {{:: ts('Edit') }} + {{:: ts('Edit') }} + {{:: ts('Delete') }}

{{:: ts('No saved searches.')}} - {{:: ts('New Search') }}

diff --git a/ext/search/ang/searchAdmin/tabs.html b/ext/search/ang/searchAdmin/tabs.html index a9d6591c01..6f581bc714 100644 --- a/ext/search/ang/searchAdmin/tabs.html +++ b/ext/search/ang/searchAdmin/tabs.html @@ -4,19 +4,19 @@ {{ ts('Compose Search') }} -
  • +
  • - {{:: ts('Smart Group:') }} {{ $ctrl.savedSearch.group.title }} + {{:: ts('Smart Group:') }} {{ $ctrl.savedSearch.groups[0].title }}
  • - + - {{ $ctrl.displayTypes[display.type].label }} + {{ display.label || ts('Untitled') }}