SearchKit - Add generic controller for ApiBatch action
authorColeman Watts <coleman@civicrm.org>
Sun, 16 Oct 2022 16:01:33 +0000 (12:01 -0400)
committerColeman Watts <coleman@civicrm.org>
Sun, 16 Oct 2022 18:27:36 +0000 (14:27 -0400)
This allows extensions to define batch actions without the need for any javascript code

ext/search_kit/Civi/Api4/Action/SearchDisplay/GetSearchTasks.php
ext/search_kit/ang/crmSearchTasks/crmSearchTaskApiBatch.ctrl.js [new file with mode: 0644]
ext/search_kit/ang/crmSearchTasks/crmSearchTaskApiBatch.html [new file with mode: 0644]
ext/search_kit/ang/crmSearchTasks/crmSearchTaskDelete.ctrl.js [deleted file]
ext/search_kit/ang/crmSearchTasks/crmSearchTaskDelete.html [deleted file]
ext/search_kit/ang/crmSearchTasks/crmSearchTasks.component.js

index ec2266c26309623288b942189636d3484719df98..d5586ec613ec4225e3bae8a79c552f00335d3823 100644 (file)
@@ -79,10 +79,16 @@ class GetSearchTasks extends \Civi\Api4\Generic\AbstractAction {
 
     if (array_key_exists('delete', $entity['actions'])) {
       $tasks[$entity['name']]['delete'] = [
-        'module' => 'crmSearchTasks',
         'title' => E::ts('Delete %1', [1 => $entity['title_plural']]),
         'icon' => 'fa-trash',
-        'uiDialog' => ['templateUrl' => '~/crmSearchTasks/crmSearchTaskDelete.html'],
+        'apiBatch' => [
+          'action' => 'delete',
+          'params' => NULL,
+          'confirmMsg' => E::ts('Are you sure you want to delete %1 %2?'),
+          'runMsg' => E::ts('Deleting %1 %2...'),
+          'successMsg' => E::ts('Successfully deleted %1 %2.'),
+          'errorMsg' => E::ts('An error occurred while attempting to delete %1 %2.'),
+        ],
       ];
     }
 
@@ -168,17 +174,49 @@ class GetSearchTasks extends \Civi\Api4\Generic\AbstractAction {
       $null, $null, $null, 'civicrm_searchKitTasks'
     );
 
-    usort($tasks[$entity['name']], function($a, $b) {
-      return strnatcasecmp($a['title'], $b['title']);
-    });
-
     foreach ($tasks[$entity['name']] as $name => &$task) {
       $task['name'] = $name;
       // Add default for number of rows action requires
       $task += ['number' => '> 0'];
     }
 
-    $result->exchangeArray(array_values($tasks[$entity['name']]));
+    usort($tasks[$entity['name']], function($a, $b) {
+      return strnatcasecmp($a['title'], $b['title']);
+    });
+
+    $result->exchangeArray($tasks[$entity['name']]);
+  }
+
+  public static function fields() {
+    return [
+      [
+        'name' => 'name',
+      ],
+      [
+        'name' => 'module',
+      ],
+      [
+        'name' => 'title',
+      ],
+      [
+        'name' => 'icon',
+      ],
+      [
+        'number' => 'icon',
+      ],
+      [
+        'name' => 'apiBatch',
+        'data_type' => 'Array',
+      ],
+      [
+        'name' => 'uiDialog',
+        'data_type' => 'Array',
+      ],
+      [
+        'name' => 'crmPopup',
+        'data_type' => 'Array',
+      ],
+    ];
   }
 
 }
diff --git a/ext/search_kit/ang/crmSearchTasks/crmSearchTaskApiBatch.ctrl.js b/ext/search_kit/ang/crmSearchTasks/crmSearchTaskApiBatch.ctrl.js
new file mode 100644 (file)
index 0000000..cb06c22
--- /dev/null
@@ -0,0 +1,28 @@
+(function(angular, $, _) {
+  "use strict";
+
+  // Generic controller for running an ApiBatch task
+  angular.module('crmSearchTasks').controller('crmSearchTaskApiBatch', function($scope, searchTaskBaseTrait) {
+    var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
+      // Combine this controller with model properties (ids, entity, entityInfo) and searchTaskBaseTrait
+      ctrl = angular.extend(this, $scope.model, searchTaskBaseTrait);
+
+    this.entityTitle = this.getEntityTitle();
+
+    // If no confirmation message, skip straight to processing
+    if (!ctrl.apiBatch.confirmMsg) {
+      ctrl.start(ctrl.apiBatch.params);
+    }
+
+    this.onSuccess = function() {
+      CRM.alert(ts(ctrl.apiBatch.successMsg, {1: ctrl.ids.length, 2: ctrl.entityTitle}), ts('%1 Complete', {1: ctrl.taskTitle}), 'success');
+      this.close();
+    };
+
+    this.onError = function() {
+      CRM.alert(ts(ctrl.apiBatch.errorMsg, {1: ctrl.ids.length, 2: ctrl.entityTitle}), ts('Error'), 'error');
+      this.cancel();
+    };
+
+  });
+})(angular, CRM.$, CRM._);
diff --git a/ext/search_kit/ang/crmSearchTasks/crmSearchTaskApiBatch.html b/ext/search_kit/ang/crmSearchTasks/crmSearchTaskApiBatch.html
new file mode 100644 (file)
index 0000000..a4ce4c5
--- /dev/null
@@ -0,0 +1,13 @@
+<div id="bootstrap-theme" crm-dialog="crmSearchTask">
+  <form ng-controller="crmSearchTaskApiBatch as $ctrl">
+    <p><strong ng-if="model.apiBatch.confirmMsg">{{:: ts(model.apiBatch.confirmMsg, {1: model.ids.length, 2: $ctrl.entityTitle}) }}</strong></p>
+    <hr />
+    <div ng-if="$ctrl.run" class="crm-search-task-progress">
+      <h5>{{:: ts(model.apiBatch.runMsg, {1: model.ids.length, 2: $ctrl.entityTitle}) }}</h5>
+      <crm-search-batch-runner entity="model.entity" action="{{:: model.apiBatch.action }}" params="$ctrl.run" ids="model.ids" success="$ctrl.onSuccess()" error="$ctrl.onError()" id-field="{{:: $ctrl.entityInfo.primary_key[0] }}"></crm-search-batch-runner>
+    </div>
+
+    <crm-dialog-button text="ts('Cancel')" icons="{primary: 'fa-times'}" on-click="$ctrl.cancel()" disabled="$ctrl.run" ></crm-dialog-button>
+    <crm-dialog-button ng-if="model.apiBatch.confirmMsg" text="model.taskTitle" icons="{primary: $ctrl.run ? 'fa-spin fa-spinner' : 'fa-check'}" on-click="$ctrl.start(model.apiBatch.params)" disabled="$ctrl.run" ></crm-dialog-button>
+  </form>
+</div>
diff --git a/ext/search_kit/ang/crmSearchTasks/crmSearchTaskDelete.ctrl.js b/ext/search_kit/ang/crmSearchTasks/crmSearchTaskDelete.ctrl.js
deleted file mode 100644 (file)
index ddce87d..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-(function(angular, $, _) {
-  "use strict";
-
-  angular.module('crmSearchTasks').controller('crmSearchTaskDelete', function($scope, searchTaskBaseTrait) {
-    var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
-      // Combine this controller with model properties (ids, entity, entityInfo) and searchTaskBaseTrait
-      ctrl = angular.extend(this, $scope.model, searchTaskBaseTrait);
-
-    this.entityTitle = this.getEntityTitle();
-
-    this.onSuccess = function() {
-      CRM.alert(ts('Successfully deleted %1 %2.', {1: ctrl.ids.length, 2: ctrl.entityTitle}), ts('Deleted'), 'success');
-      this.close();
-    };
-
-    this.onError = function() {
-      CRM.alert(ts('An error occurred while attempting to delete %1 %2.', {1: ctrl.ids.length, 2: ctrl.entityTitle}), ts('Error'), 'error');
-      this.cancel();
-    };
-
-  });
-})(angular, CRM.$, CRM._);
diff --git a/ext/search_kit/ang/crmSearchTasks/crmSearchTaskDelete.html b/ext/search_kit/ang/crmSearchTasks/crmSearchTaskDelete.html
deleted file mode 100644 (file)
index e15415c..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<div id="bootstrap-theme" crm-dialog="crmSearchTask">
-  <form ng-controller="crmSearchTaskDelete as $ctrl">
-    <p><strong>{{:: ts('Are you sure you want to delete %1 %2?', {1: model.ids.length, 2: $ctrl.entityTitle}) }}</strong></p>
-    <hr />
-    <div ng-if="$ctrl.run" class="crm-search-task-progress">
-      <h5>{{:: ts('Deleting %1 %2...', {1: model.ids.length, 2: $ctrl.entityTitle}) }}</h5>
-      <crm-search-batch-runner entity="model.entity" action="delete" params="$ctrl.run" ids="model.ids" success="$ctrl.onSuccess()" error="$ctrl.onError()" ></crm-search-batch-runner>
-    </div>
-
-    <crm-dialog-button text="ts('Cancel')" icons="{primary: 'fa-times'}" on-click="$ctrl.cancel()" disabled="$ctrl.run" ></crm-dialog-button>
-    <crm-dialog-button text="ts('Delete %1', {1: $ctrl.entityTitle})" icons="{primary: $ctrl.run ? 'fa-spin fa-spinner' : 'fa-trash'}" on-click="$ctrl.start()" disabled="$ctrl.run" ></crm-dialog-button>
-  </form>
-</div>
index 639bc4cfddf76d6cfd69b9c65563b4ccf9a1cfe6..83d189c5d669724af76db7f9def77319fe403846 100644 (file)
@@ -30,7 +30,7 @@
         }
         initialized = true;
         crmApi4({
-          entityInfo: ['Entity', 'get', {select: ['name', 'title', 'title_plural'], where: [['name', '=', ctrl.entity]]}, 0],
+          entityInfo: ['Entity', 'get', {select: ['name', 'title', 'title_plural', 'primary_key'], where: [['name', '=', ctrl.entity]]}, 0],
           tasks: ['SearchDisplay', 'getSearchTasks', {entity: ctrl.entity}]
         }).then(function(result) {
           ctrl.entityInfo = result.entityInfo;
@@ -61,7 +61,9 @@
           search: ctrl.search,
           display: ctrl.display,
           displayController: ctrl.displayController,
-          entityInfo: ctrl.entityInfo
+          entityInfo: ctrl.entityInfo,
+          taskTitle: action.title,
+          apiBatch: _.cloneDeep(action.apiBatch)
         };
         // If action uses a crmPopup form
         if (action.crmPopup) {
             .on('crmFormSuccess', ctrl.refresh);
         }
         // If action uses dialogService
-        else if (action.uiDialog) {
+        else {
           var options = CRM.utils.adjustDialogDefaults({
             autoOpen: false,
             dialogClass: 'crm-search-task-dialog',
             title: action.title
           });
-          dialogService.open('crmSearchTask', action.uiDialog.templateUrl, data, options)
+          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);
         }