SearchKit - Refactor bulk task management into a class
authorcolemanw <coleman@civicrm.org>
Sun, 28 May 2023 03:21:26 +0000 (23:21 -0400)
committercolemanw <coleman@civicrm.org>
Tue, 11 Jul 2023 21:08:03 +0000 (17:08 -0400)
15 files changed:
ext/search_kit/ang/crmSearchAdmin/resultsTable/crmSearchAdminResultsTable.component.js
ext/search_kit/ang/crmSearchAdmin/resultsTable/crmSearchAdminResultsTable.html
ext/search_kit/ang/crmSearchAdmin/searchListing/crmSearchAdminSearchListing.component.js
ext/search_kit/ang/crmSearchAdmin/searchSegmentListing/crmSearchAdminSegmentListing.component.js
ext/search_kit/ang/crmSearchDisplay/colType/field.html
ext/search_kit/ang/crmSearchDisplay/traits/searchDisplayBaseTrait.service.js
ext/search_kit/ang/crmSearchDisplayTable/crmSearchDisplayTable.component.js
ext/search_kit/ang/crmSearchDisplayTable/crmSearchDisplayTable.html
ext/search_kit/ang/crmSearchTasks/crmSearchTaskDownload.ctrl.js
ext/search_kit/ang/crmSearchTasks/crmSearchTaskDownload.html
ext/search_kit/ang/crmSearchTasks/crmSearchTasks.component.js [deleted file]
ext/search_kit/ang/crmSearchTasks/crmSearchTasksMenu.component.js [new file with mode: 0644]
ext/search_kit/ang/crmSearchTasks/crmSearchTasksMenu.html [moved from ext/search_kit/ang/crmSearchTasks/crmSearchTasks.html with 51% similarity]
ext/search_kit/ang/crmSearchTasks/traits/searchDisplayTasksTrait.service.js
ext/search_kit/ang/crmSearchTasks/traits/searchTaskBaseTrait.service.js

index a9ba4054221bbc7ff26c03d0b1fb709ac481151d..68c2dbda744430432489dfb117e67659351de238 100644 (file)
       };
 
       // Add callbacks for pre & post run
-      this.onPreRun.push(function(apiParams) {
-        apiParams.debug = true;
+      this.onPreRun.push(function(apiCalls) {
+        apiCalls.run.debug = true;
       });
 
-      this.onPostRun.push(function(result) {
-        ctrl.debug = _.extend(_.pick(ctrl.debug, 'apiParams'), result.debug);
+      this.onPostRun.push(function(apiResults) {
+        ctrl.debug = _.extend(_.pick(ctrl.debug, 'apiParams'), apiResults.run.debug);
       });
 
       $scope.sortableColumnOptions = {
index da300b4a967d5677d2856dd4833c1990e8c4445e..34fcf8dd8478487ab46df3124097aed80a093085 100644 (file)
@@ -2,7 +2,7 @@
   <div ng-include="'~/crmSearchAdmin/resultsTable/debug.html'"></div>
   <div class="form-inline">
     <div class="btn-group" ng-include="'~/crmSearchDisplay/SearchButton.html'"></div>
-    <crm-search-tasks entity="$ctrl.apiEntity" ids="$ctrl.selectedRows" search="$ctrl.search" display="$ctrl.display" display-controller="$ctrl" refresh="$ctrl.refreshAfterTask()"></crm-search-tasks>
+    <crm-search-tasks-menu ids="$ctrl.selectedRows" task-manager="$ctrl.taskManager"></crm-search-tasks-menu>
     <span ng-include="'~/crmSearchDisplay/ResultCount.html'"></span>
   </div>
   <table class="{{:: $ctrl.settings.classes.join(' ') }}">
index 8c23e2f94b2d90772f22353eb9d127d803a9ea94..977e965f6940afb34596d3e49416b79d43436c0b 100644 (file)
@@ -87,8 +87,8 @@
         }));
       }
 
-      this.onPostRun.push(function(result) {
-        _.each(result, function(row) {
+      this.onPostRun.push(function(apiResults) {
+        _.each(apiResults.run, function(row) {
           row.permissionToEdit = CRM.checkPerm('all CiviCRM permissions and ACLs') || !_.includes(row.data.display_acl_bypass, true);
           // If main entity doesn't exist, no can edit
           if (!row.data['api_entity:label']) {
 
       this.deleteSearch = function(row) {
         ctrl.runSearch(
-          [['SavedSearch', 'delete', {where: [['id', '=', row.key]]}]],
+          {deleteSearch: ['SavedSearch', 'delete', {where: [['id', '=', row.key]]}]},
           {start: ts('Deleting...'), success: ts('Search Deleted')},
           row
         );
 
       this.revertSearch = function(row) {
         ctrl.runSearch(
-          [['SavedSearch', 'revert', {
+          {revertSearch: ['SavedSearch', 'revert', {
             where: [['id', '=', row.key]],
             chain: {
               revertDisplays: ['SearchDisplay', 'revert', {'where': [['saved_search_id', '=', '$id'], ['has_base', '=', true]]}],
               deleteDisplays: ['SearchDisplay', 'delete', {'where': [['saved_search_id', '=', '$id'], ['has_base', '=', false]]}]
             }
-          }]],
+          }]},
           {start: ts('Reverting...'), success: ts('Search Reverted')},
           row
         );
index c72ef73ada7b009ef90442c57cbaf646eb99aa0b..9cf52d621b0075da78487f81f6929c69d3100a05 100644 (file)
@@ -40,7 +40,7 @@
 
       this.deleteSegment = function(row) {
         ctrl.runSearch(
-          [['SearchSegment', 'delete', {where: [['id', '=', row.key]]}]],
+          {deleteSegment: ['SearchSegment', 'delete', {where: [['id', '=', row.key]]}]},
           {start: ts('Deleting...'), success: ts('Segment Deleted')},
           row
         );
index e0493dc0ea72018a600f57b099b51f162e2a24c4..f2d6d2cd610b56a8724d60e60ac28ca48fca7c12 100644 (file)
@@ -1,4 +1,4 @@
-<crm-search-display-editable row="row" col="colData" do-save="$ctrl.runSearch([apiCall], {}, row)" cancel="$ctrl.editing = null;" ng-if="colData.edit && $ctrl.editing && $ctrl.editing[0] === rowIndex && $ctrl.editing[1] === colIndex"></crm-search-display-editable>
+<crm-search-display-editable row="row" col="colData" do-save="$ctrl.runSearch({inPlaceEdit: apiCall}, {}, row)" cancel="$ctrl.editing = null;" ng-if="colData.edit && $ctrl.editing && $ctrl.editing[0] === rowIndex && $ctrl.editing[1] === colIndex"></crm-search-display-editable>
 <span ng-if="::!colData.links" ng-class="{'crm-editable-enabled': colData.edit && !$ctrl.editing, 'crm-editable-disabled': colData.edit && $ctrl.editing}" ng-click="colData.edit && !$ctrl.editing && ($ctrl.editing = [rowIndex, colIndex])">
   <i ng-repeat="icon in colData.icons" ng-if="icon.side === 'left'" class="crm-i {{:: icon['class'] }}"></i>
   {{:: $ctrl.formatFieldValue(colData) }}
index d40b30ce6b210d4e55090334e03e549a7df10686..617a8a841adc13375013bc6b96546a1435702186 100644 (file)
@@ -10,6 +10,7 @@
       page: 1,
       rowCount: null,
       // Arrays may contain callback functions for various events
+      onInitialize: [],
       onChangeFilters: [],
       onPreRun: [],
       onPostRun: [],
@@ -26,6 +27,9 @@
         for (var p=0; p < placeholderCount; ++p) {
           this.placeholders.push({});
         }
+        _.each(ctrl.onInitialize, function(callback) {
+          callback.call(ctrl, $scope, $element);
+        });
 
         // _.debounce used here to trigger the initial search immediately but prevent subsequent launches within 300ms
         this.getResultsPronto = _.debounce(ctrl.runSearch, 300, {leading: true, trailing: false});
@@ -81,9 +85,9 @@
         if (this.afFieldset) {
           $scope.$watch(this.afFieldset.getFieldData, onChangeFilters, true);
           // Add filter title to Afform
-          this.onPostRun.push(function(results) {
-            if (results.labels && results.labels.length && $scope.$parent.addTitle) {
-              $scope.$parent.addTitle(results.labels.join(' '));
+          this.onPostRun.push(function(apiResults) {
+            if (apiResults.run.labels && apiResults.run.labels.length && $scope.$parent.addTitle) {
+              $scope.$parent.addTitle(apiResults.run.labels.join(' '));
             }
           });
         }
         if (!statusParams) {
           this.loading = true;
         }
+        apiCalls = apiCalls || {};
+        apiCalls.run = ['SearchDisplay', 'run', apiParams];
         _.each(ctrl.onPreRun, function(callback) {
-          callback.call(ctrl, apiParams);
+          callback.call(ctrl, apiCalls);
         });
-        apiCalls = apiCalls || [];
-        apiCalls.push(['SearchDisplay', 'run', apiParams]);
         var apiRequest = crmApi4(apiCalls);
         apiRequest.then(function(apiResults) {
           if (requestId < ctrl._runCount) {
             return; // Another request started after this one
           }
-          ctrl.results = _.last(apiResults);
+          ctrl.results = apiResults.run;
           ctrl.editing = ctrl.loading = false;
           // Update rowCount if running for the first time or during an update op
           if (!ctrl.rowCount || editedRow) {
             }
           }
           _.each(ctrl.onPostRun, function(callback) {
-            callback.call(ctrl, ctrl.results, 'success', editedRow);
+            callback.call(ctrl, apiResults, 'success', editedRow);
           });
         }, function(error) {
           if (requestId < ctrl._runCount) {
index 04f8120ffa88a7eda3de7c0750de028cd044a778..59372abd768ccce312a7b759c671c4c19d97430c 100644 (file)
         var tallyParams;
 
         if (ctrl.settings.tally) {
-          ctrl.onPreRun.push(function (apiParams) {
+          ctrl.onPreRun.push(function (apiCalls) {
             ctrl.tally = null;
-            tallyParams = _.cloneDeep(apiParams);
+            tallyParams = _.cloneDeep(apiCalls.run);
           });
 
-          ctrl.onPostRun.push(function (results, status) {
+          ctrl.onPostRun.push(function (apiResults, status) {
             ctrl.tally = null;
             if (status === 'success' && tallyParams) {
               tallyParams.return = 'tally';
@@ -65,7 +65,7 @@
                   updateParams = {where: [['id', '=', movedItem.data.id]], values: {}};
                 if (newPosition > -1 && oldPosition !== newPosition) {
                   updateParams.values[weightColumn] = displacedItem.data[weightColumn];
-                  ctrl.runSearch([[ctrl.apiEntity, 'update', updateParams]], {}, movedItem);
+                  ctrl.runSearch({updateWeight: [ctrl.apiEntity, 'update', updateParams]}, {}, movedItem);
                 }
               });
             }
index d9df2cda7595e05e7458019ee77b6775ae981354..8f9437ea41ecfc20e9eac1128a3d6dc7a8841890 100644 (file)
@@ -2,7 +2,7 @@
   <div class="alert alert-info crm-search-display-description" ng-if="$ctrl.settings.description">{{:: $ctrl.settings.description }}</div>
   <div class="form-inline">
     <div class="btn-group" ng-include="'~/crmSearchDisplay/SearchButton.html'" ng-if="$ctrl.settings.button"></div>
-    <crm-search-tasks ng-if="$ctrl.settings.actions" entity="$ctrl.apiEntity" ids="$ctrl.selectedRows" search="$ctrl.search" display="$ctrl.display" display-controller="$ctrl" refresh="$ctrl.refreshAfterTask()"></crm-search-tasks>
+    <crm-search-tasks-menu ng-if="$ctrl.settings.actions && $ctrl.taskManager" ids="$ctrl.selectedRows" task-manager="$ctrl.taskManager"></crm-search-tasks-menu>
     <span ng-if="$ctrl.settings.headerCount" ng-include="'~/crmSearchDisplay/ResultCount.html'"></span>
     <div class="btn-group pull-right" ng-include="'~/crmSearchDisplay/AddButton.html'" ng-if="$ctrl.settings.addButton.path"></div>
   </div>
index 8482eca9fef27b4d7f2686876bf39424bf2b4786..3166bce5f6e907fedbdf9ab909eccd0cd228949a 100644 (file)
@@ -19,7 +19,7 @@
           ctrl.progress += 10;
         }
       }, 1000);
-      var apiParams = ctrl.displayController.getApiParams();
+      var apiParams = ctrl.taskManager.getApiParams();
       delete apiParams.return;
       delete apiParams.limit;
       apiParams.filters.id = ctrl.ids || null;
index 1271deda0c6e4a6a786e6555907088c5af40acfc..5a510d16dbd7bb2cc5c020efb7df6b1188385e15 100644 (file)
@@ -2,7 +2,7 @@
   <form ng-controller="crmSearchTaskDownload as $ctrl">
     <p>
       <strong ng-if="$ctrl.ids.length">{{:: ts('Download %1 %2', {1: $ctrl.ids.length, 2: $ctrl.entityTitle}) }}</strong>
-      <strong ng-if="!$ctrl.ids.length">{{:: ts('Download %1 %2', {1: $ctrl.displayController.rowCount, 2: $ctrl.entityTitle}) }}</strong>
+      <strong ng-if="!$ctrl.ids.length">{{:: ts('Download %1 %2', {1: $ctrl.taskManager.getRowCount(), 2: $ctrl.entityTitle}) }}</strong>
     </p>
     <div class="form-inline">
       <label for="crmSearchTaskDownload-format">{{:: ts('Format') }}</label>
diff --git a/ext/search_kit/ang/crmSearchTasks/crmSearchTasks.component.js b/ext/search_kit/ang/crmSearchTasks/crmSearchTasks.component.js
deleted file mode 100644 (file)
index 6b1d7d5..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-(function(angular, $, _) {
-  "use strict";
-
-  angular.module('crmSearchTasks').component('crmSearchTasks', {
-    bindings: {
-      entity: '<',
-      refresh: '&',
-      search: '<',
-      display: '<',
-      displayController: '<',
-      ids: '<'
-    },
-    templateUrl: '~/crmSearchTasks/crmSearchTasks.html',
-    controller: function($scope, crmApi4, dialogService, $window) {
-      var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
-        ctrl = this,
-        initialized = false,
-        unwatchIDs = $scope.$watch('$ctrl.ids.length', watchIDs);
-
-      function watchIDs() {
-        if (ctrl.ids && ctrl.ids.length) {
-          unwatchIDs();
-          ctrl.getTasks();
-        }
-      }
-
-      this.getTasks = function() {
-        if (initialized) {
-          return;
-        }
-        initialized = true;
-        crmApi4({
-          entityInfo: ['Entity', 'get', {select: ['name', 'title', 'title_plural', 'primary_key'], where: [['name', '=', ctrl.entity]]}, 0],
-          tasks: ['SearchDisplay', 'getSearchTasks', {entity: ctrl.entity, savedSearch: ctrl.search, display: ctrl.display}]
-        }).then(function(result) {
-          ctrl.entityInfo = result.entityInfo;
-          ctrl.tasks = result.tasks;
-        });
-      };
-
-      this.isActionAllowed = function(action) {
-        return $scope.$eval('' + ctrl.ids.length + action.number);
-      };
-
-      this.updateActionData = function() {
-        if (this.entity === 'RelationshipCache') {
-          this.entity = 'Relationship';
-          this.entityInfo.title = ts('Relationship');
-          this.entityInfo.title_plural = ts('Relationships');
-        }
-      };
-
-      this.getActionTitle = function(action) {
-        if (ctrl.isActionAllowed(action)) {
-          ctrl.updateActionData();
-          return ctrl.ids.length ?
-            ts('Perform action on %1 %2', {1: ctrl.ids.length, 2: ctrl.entityInfo[ctrl.ids.length === 1 ? 'title' : 'title_plural']}) :
-            ts('Perform action on all %1', {1: ctrl.entityInfo.title_plural});
-        }
-        return ts('Selected number must be %1', {1: action.number.replace('===', '')});
-      };
-
-      this.doAction = function(action) {
-        if (!ctrl.isActionAllowed(action)) {
-          return;
-        }
-        // Update data specific to entity actions.
-        ctrl.updateActionData();
-
-        var data = {
-          ids: ctrl.ids,
-          entity: ctrl.entity,
-          search: ctrl.search,
-          display: ctrl.display,
-          displayController: ctrl.displayController,
-          entityInfo: ctrl.entityInfo,
-          taskTitle: action.title,
-          apiBatch: _.cloneDeep(action.apiBatch)
-        };
-        // If action uses a crmPopup form
-        if (action.crmPopup) {
-          var path = $scope.$eval(action.crmPopup.path, data),
-            query = action.crmPopup.query && $scope.$eval(action.crmPopup.query, data);
-          CRM.loadForm(CRM.url(path, query), {post: action.crmPopup.data && $scope.$eval(action.crmPopup.data, data)})
-            .on('crmFormSuccess', ctrl.refresh);
-        }
-        else if (action.redirect) {
-          var redirectPath = $scope.$eval(action.redirect.path, data),
-            redirectQuery = action.redirect.query && $scope.$eval(action.redirect.query, data) && $scope.$eval(action.redirect.data, data);
-          $window.open(CRM.url(redirectPath, redirectQuery), '_blank');
-        }
-        // If action uses dialogService
-        else {
-          var options = CRM.utils.adjustDialogDefaults({
-            autoOpen: false,
-            dialogClass: 'crm-search-task-dialog',
-            title: action.title
-          });
-          dialogService.open('crmSearchTask', (action.uiDialog && action.uiDialog.templateUrl) || '~/crmSearchTasks/crmSearchTaskApiBatch.html', data, options)
-            // Reload results on success, do nothing on cancel
-            .then(ctrl.refresh, _.noop);
-        }
-      };
-    }
-  });
-
-})(angular, CRM.$, CRM._);
diff --git a/ext/search_kit/ang/crmSearchTasks/crmSearchTasksMenu.component.js b/ext/search_kit/ang/crmSearchTasks/crmSearchTasksMenu.component.js
new file mode 100644 (file)
index 0000000..f8fd366
--- /dev/null
@@ -0,0 +1,46 @@
+(function(angular, $, _) {
+  "use strict";
+
+  angular.module('crmSearchTasks').component('crmSearchTasksMenu', {
+    bindings: {
+      taskManager: '<',
+      ids: '<'
+    },
+    templateUrl: '~/crmSearchTasks/crmSearchTasksMenu.html',
+    controller: function($scope) {
+      var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
+        ctrl = this;
+
+      this.$onInit = function() {
+        // When a row is selected for bulk actions, load the actions menu
+        var unwatchIDs = $scope.$watch('$ctrl.ids.length', function (idsLength) {
+          if (idsLength) {
+            unwatchIDs();
+            ctrl.taskManager.getMetadata();
+          }
+        });
+      };
+
+      this.isActionAllowed = function(action) {
+        return $scope.$eval('' + ctrl.ids.length + action.number);
+      };
+
+      this.getActionTitle = function(action) {
+        if (ctrl.isActionAllowed(action)) {
+          return ctrl.ids.length ?
+            ts('Perform action on %1 %2', {1: ctrl.ids.length, 2: ctrl.taskManager.entityInfo[ctrl.ids.length === 1 ? 'title' : 'title_plural']}) :
+            ts('Perform action on all %1', {1: ctrl.taskManager.entityInfo.title_plural});
+        }
+        return ts('Selected number must be %1', {1: action.number.replace('===', '')});
+      };
+
+      this.doAction = function(action) {
+        if (!ctrl.isActionAllowed(action)) {
+          return;
+        }
+        ctrl.taskManager.doTask(action, ctrl.ids);
+      };
+    }
+  });
+
+})(angular, CRM.$, CRM._);
similarity index 51%
rename from ext/search_kit/ang/crmSearchTasks/crmSearchTasks.html
rename to ext/search_kit/ang/crmSearchTasks/crmSearchTasksMenu.html
index e2b5e4dc425e4de74f528024399360ff3f7cacc9..8f460d1519d73b9dc22401f745b3fa60f7d9cc54 100644 (file)
@@ -1,7 +1,7 @@
 <div class="btn-group">
   <button type="button"
-    ng-disabled="$ctrl.displayController.loading || !$ctrl.displayController.results.length"
-    ng-click="$ctrl.getTasks()"
+    ng-disabled="!$ctrl.taskManager.isDisplayReady()"
+    ng-click="$ctrl.openMenu = true; $ctrl.taskManager.getMetadata();"
     class="btn dropdown-toggle btn-default"
     title="{{:: ts('Perform action on selected items.') }}"
     data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
@@ -9,11 +9,11 @@
     <i class="crm-i fa-pencil"></i>
     {{:: ts('Action') }} <span class="caret"></span>
   </button>
-  <ul class="dropdown-menu">
-    <li ng-class="{disabled: !$ctrl.isActionAllowed(action)}" ng-repeat="action in $ctrl.tasks" title="{{ $ctrl.getActionTitle(action) }}">
-      <a href ng-click="$ctrl.doAction(action)"><i class="crm-i {{:: action.icon }}"></i> {{:: action.title }}</a>
+  <ul class="dropdown-menu" ng-if="$ctrl.openMenu">
+    <li ng-class="{disabled: !$ctrl.isActionAllowed(action)}" ng-repeat="action in $ctrl.taskManager.tasks" title="{{ $ctrl.getActionTitle(action) }}">
+      <a href ng-click="$ctrl.doAction(action); $ctrl.openMenu = false;"><i class="crm-i {{:: action.icon }}"></i> {{:: action.title }}</a>
     </li>
-    <li class="disabled" ng-if="!$ctrl.tasks">
+    <li class="disabled" ng-if="!$ctrl.taskManager.tasks">
       <a href><i class="crm-i fa-spinner fa-spin"></i></a>
     </li>
   </ul>
index 4e9ff99c0412c2371548b83000ab076ee830eb1e..d7e916b919b2195b359b608386322ee4b7d21c39 100644 (file)
@@ -1,10 +1,88 @@
-(function(angular, $, _) {
+ (function(angular, $, _) {
   "use strict";
 
   // Trait shared by any search display controllers which use tasks
-  angular.module('crmSearchDisplay').factory('searchDisplayTasksTrait', function(crmApi4) {
+  angular.module('crmSearchTasks').factory('searchDisplayTasksTrait', function($rootScope, $window, crmApi4, dialogService) {
     var ts = CRM.ts('org.civicrm.search_kit');
 
+    // TaskManager object is responsible for fetching task metadata for a SearchDispaly
+    // and handles the running of tasks.
+    function TaskManager(displayCtrl) {
+      var mngr = this;
+      var fetchedMetadata;
+      this.tasks = null;
+      this.entityInfo = null;
+
+      this.getMetadata = function() {
+        if (fetchedMetadata) {
+          return;
+        }
+        fetchedMetadata = crmApi4({
+          entityInfo: ['Entity', 'get', {select: ['name', 'title', 'title_plural', 'primary_key'], where: [['name', '=', mngr.getEntityName()]]}, 0],
+          tasks: ['SearchDisplay', 'getSearchTasks', {entity: displayCtrl.apiEntity, savedSearch: displayCtrl.search, display: displayCtrl.display}]
+        }).then(function(result) {
+          mngr.entityInfo = result.entityInfo;
+          mngr.tasks = result.tasks;
+        });
+      };
+
+      this.getEntityName = function() {
+        return displayCtrl.apiEntity === 'RelationshipCache' ? 'Relationship' : displayCtrl.apiEntity;
+      };
+      this.getApiParams = function() {
+        return displayCtrl.getApiParams();
+      };
+      this.getRowCount = function() {
+        return displayCtrl.rowCount;
+      };
+      this.isDisplayReady = function() {
+        return !displayCtrl.loading && displayCtrl.results && displayCtrl.results.length;
+      };
+
+      this.doTask = function(task, ids) {
+        var data = {
+          ids: ids,
+          entity: mngr.getEntityName(),
+          search: displayCtrl.search,
+          display: displayCtrl.display,
+          taskManager: mngr,
+          entityInfo: mngr.entityInfo,
+          taskTitle: task.title,
+          apiBatch: _.cloneDeep(task.apiBatch)
+        };
+        // If task uses a crmPopup form
+        if (task.crmPopup) {
+          var path = $rootScope.$eval(task.crmPopup.path, data),
+            query = task.crmPopup.query && $rootScope.$eval(task.crmPopup.query, data);
+          CRM.loadForm(CRM.url(path, query), {post: task.crmPopup.data && $rootScope.$eval(task.crmPopup.data, data)})
+            .on('crmFormSuccess', mngr.refreshAfterTask);
+        }
+        else if (task.redirect) {
+          var redirectPath = $rootScope.$eval(task.redirect.path, data),
+            redirectQuery = task.redirect.query && $rootScope.$eval(task.redirect.query, data) && $rootScope.$eval(task.redirect.data, data);
+          $window.open(CRM.url(redirectPath, redirectQuery), '_blank');
+        }
+        // If task uses dialogService
+        else {
+          var options = CRM.utils.adjustDialogDefaults({
+            autoOpen: false,
+            dialogClass: 'crm-search-task-dialog',
+            title: task.title
+          });
+          dialogService.open('crmSearchTask', (task.uiDialog && task.uiDialog.templateUrl) || '~/crmSearchTasks/crmSearchTaskApiBatch.html', data, options)
+            // Reload results on success, do nothing on cancel
+            .then(mngr.refreshAfterTask, _.noop);
+        }
+      };
+
+      this.refreshAfterTask = function() {
+        displayCtrl.selectedRows = [];
+        displayCtrl.allRowsSelected = false;
+        displayCtrl.rowCount = undefined;
+        displayCtrl.runSearch();
+      };
+    }
+
     // Trait properties get mixed into display controller using angular.extend()
     return {
 
           (!this.allRowsSelected && this.selectedRows && this.selectedRows.length === this.results.length);
       },
 
-      refreshAfterTask: function() {
-        this.selectedRows = [];
-        this.allRowsSelected = false;
-        this.rowCount = undefined;
-        this.runSearch();
-      },
+      // onInitialize callback
+      onInitialize: [function() {
+        // Instantiate task manager object
+        if (!this.taskManager) {
+          this.taskManager = new TaskManager(this);
+        }
+      }],
 
-      // Add onChangeFilters callback (gets merged with others via angular.extend)
+      // onChangeFilters callback
       onChangeFilters: [function() {
         // Reset selection when filters are changed
         this.selectedRows = [];
         this.allRowsSelected = false;
       }],
 
-      // Add onPostRun callback (gets merged with others via angular.extend)
-      onPostRun: [function(results, status, editedRow) {
+      // onPostRun callback (gets merged with others via angular.extend)
+      onPostRun: [function(apiResults, status, editedRow) {
         if (editedRow && status === 'success' && this.selectedRows) {
           // If edited row disappears (because edits cause it to not meet search criteria), deselect it
           var index = this.selectedRows.indexOf(editedRow.key);
-          if (index > -1 && !_.findWhere(results, {key: editedRow.key})) {
+          if (index > -1 && !_.findWhere(apiResults.run, {key: editedRow.key})) {
             this.selectedRows.splice(index, 1);
           }
         }
index 68149e0f4a3445e585f19d365339b9b721c884b8..c7749532e0e5810e284c46c617da947af9457be9 100644 (file)
@@ -2,7 +2,7 @@
   "use strict";
 
   // Trait shared by task controllers
-  angular.module('crmSearchDisplay').factory('searchTaskBaseTrait', function(dialogService) {
+  angular.module('crmSearchTasks').factory('searchTaskBaseTrait', function(dialogService) {
     var ts = CRM.ts('org.civicrm.search_kit');
 
     // Trait properties get mixed into task controller using angular.extend()