Refactor SearchKit displays to inherit traits from a common base
authorColeman Watts <coleman@civicrm.org>
Mon, 19 Jul 2021 03:58:21 +0000 (23:58 -0400)
committerColeman Watts <coleman@civicrm.org>
Mon, 19 Jul 2021 14:38:05 +0000 (10:38 -0400)
This refactors searchDisplayUtils to searchDisplayBaseTrait service,
which acts like a PHP trait, controllers use it via angular.extend().

ext/search_kit/ang/crmSearchDisplay.module.js
ext/search_kit/ang/crmSearchDisplay/Pager.html
ext/search_kit/ang/crmSearchDisplay/colType/buttons.html
ext/search_kit/ang/crmSearchDisplay/colType/links.html
ext/search_kit/ang/crmSearchDisplay/colType/menu.html
ext/search_kit/ang/crmSearchDisplayList/crmSearchDisplayList.component.js
ext/search_kit/ang/crmSearchDisplayList/crmSearchDisplayListItems.html
ext/search_kit/ang/crmSearchDisplayTable/crmSearchDisplayTable.component.js
ext/search_kit/ang/crmSearchDisplayTable/crmSearchDisplayTable.html

index 64b6bd3c86ec9557c90ab876d530e5ee8095d357..14b863f67b591d2efc6e632640960ae24dfb6d05 100644 (file)
@@ -4,7 +4,9 @@
   // Declare module
   angular.module('crmSearchDisplay', CRM.angRequires('crmSearchDisplay'))
 
-    .factory('searchDisplayUtils', function(crmApi4) {
+    // Provides base methods and properties common to all search display types
+    .factory('searchDisplayBaseTrait', function(crmApi4) {
+      var ts = CRM.ts('org.civicrm.search_kit');
 
       // Replace tokens keyed to rowData.
       // If rowMeta is provided, values will be formatted; if omitted, raw values will be provided.
         return result;
       }
 
-      function getApiParams(ctrl, mode) {
-        return {
-          return: mode || 'page:' + ctrl.page,
-          savedSearch: ctrl.search,
-          display: ctrl.display,
-          sort: ctrl.sort,
-          filters: _.assign({}, (ctrl.afFieldset ? ctrl.afFieldset.getFieldData() : {}), ctrl.filters),
-          afform: ctrl.afFieldset ? ctrl.afFieldset.getFormName() : null
-        };
-      }
+      // Return a base trait shared by all search display controllers
+      // Gets mixed in using angular.extend()
+      return {
+        page: 1,
+        rowCount: null,
+        getUrl: getUrl,
+
+        // Called by the controller's $onInit function
+        initializeDisplay: function($scope, $element) {
+          var ctrl = this;
+          this.sort = this.settings.sort ? _.cloneDeep(this.settings.sort) : [];
+
+          $scope.getResults = _.debounce(function() {
+            ctrl.getResults();
+          }, 100);
 
-      function getResults(ctrl) {
-        return crmApi4('SearchDisplay', 'run', getApiParams(ctrl)).then(function(results) {
-          ctrl.results = results;
-          ctrl.editing = false;
-          if (!ctrl.rowCount) {
-            if (!ctrl.settings.limit || results.length < ctrl.settings.limit) {
-              ctrl.rowCount = results.length;
-            } else if (ctrl.settings.pager) {
-              var params = getApiParams(ctrl, 'row_count');
-              crmApi4('SearchDisplay', 'run', params).then(function(result) {
-                ctrl.rowCount = result.count;
-              });
+          // If search is embedded in contact summary tab, display count in tab-header
+          var contactTab = $element.closest('.crm-contact-page .ui-tabs-panel').attr('id');
+          if (contactTab) {
+            var unwatchCount = $scope.$watch('$ctrl.rowCount', function(rowCount) {
+              if (typeof rowCount === 'number') {
+                unwatchCount();
+                CRM.tabHeader.updateCount(contactTab.replace('contact-', '#tab_'), rowCount);
+              }
+            });
+          }
+
+          function onChangeFilters() {
+            ctrl.page = 1;
+            ctrl.rowCount = null;
+            if (ctrl.onChangeFilters) {
+              ctrl.onChangeFilters();
             }
+            $scope.getResults();
           }
-        });
-      }
 
-      return {
-        formatDisplayValue: formatDisplayValue,
-        formatLinks: formatLinks,
-        getApiParams: getApiParams,
-        getResults: getResults,
-        replaceTokens: replaceTokens,
-        getUrl: getUrl
+          if (this.afFieldset) {
+            $scope.$watch(this.afFieldset.getFieldData, onChangeFilters, true);
+          }
+          $scope.$watch('$ctrl.filters', onChangeFilters, true);
+        },
+
+        // Generate params for the SearchDisplay.run api
+        getApiParams: function(mode) {
+          return {
+            return: mode || 'page:' + this.page,
+            savedSearch: this.search,
+            display: this.display,
+            sort: this.sort,
+            filters: _.assign({}, (this.afFieldset ? this.afFieldset.getFieldData() : {}), this.filters),
+            afform: this.afFieldset ? this.afFieldset.getFormName() : null
+          };
+        },
+
+        // Call SearchDisplay.run and update ctrl.results and ctrl.rowCount
+        getResults: function() {
+          var ctrl = this;
+          return crmApi4('SearchDisplay', 'run', ctrl.getApiParams()).then(function(results) {
+            ctrl.results = results;
+            ctrl.editing = false;
+            if (!ctrl.rowCount) {
+              if (!ctrl.settings.limit || results.length < ctrl.settings.limit) {
+                ctrl.rowCount = results.length;
+              } else if (ctrl.settings.pager) {
+                var params = ctrl.getApiParams('row_count');
+                crmApi4('SearchDisplay', 'run', params).then(function(result) {
+                  ctrl.rowCount = result.count;
+                });
+              }
+            }
+          });
+        },
+        replaceTokens: function(value, row) {
+          return replaceTokens(value, row, this.settings.columns);
+        },
+        getLinks: function(rowData, col) {
+          rowData._links = rowData._links || {};
+          if (!(col.key in rowData._links)) {
+            rowData._links[col.key] = formatLinks(rowData, col.key, this.settings.columns);
+          }
+          return rowData._links[col.key];
+        },
+        formatFieldValue: function(rowData, col) {
+          return formatDisplayValue(rowData, col.key, this.settings.columns);
+        }
       };
     });
 
index d760bec287fd6f56bfaf7ea3d3b69820ee74961f..868bc8b855822c2521c0dee5d530acd6590a67a9 100644 (file)
@@ -4,7 +4,7 @@
       boundary-links="true"
       total-items="$ctrl.rowCount"
       ng-model="$ctrl.page"
-      ng-change="$ctrl.getResults()"
+      ng-change="getResults()"
       items-per-page="$ctrl.settings.limit"
       max-size="6"
       force-ellipses="true"
index 4909f20dbff5ccb316c302bcc7e5a73449eada73..1c178db16d86b86f2a1d093671ef8021af3e02fb 100644 (file)
@@ -1,5 +1,5 @@
 <span ng-repeat="item in col.links">
-  <a class="btn {{:: col.size }} btn-{{:: item.style }}" target="{{:: item.target }}" href="{{:: displayUtils.getUrl(item.path, row) }}">
+  <a class="btn {{:: col.size }} btn-{{:: item.style }}" target="{{:: item.target }}" href="{{:: $ctrl.getUrl(item.path, row) }}">
     <i ng-if=":: item.icon" class="crm-i {{:: item.icon }}"></i>
     {{:: $ctrl.replaceTokens(item.text, row) }}
   </a>
index b4a6dea2204385ebb8e149186d5507b3185e8a3f..5d59a7960d5b082e4216a33f7f4c9bbabaa846b6 100644 (file)
@@ -1,5 +1,5 @@
 <span ng-repeat="item in col.links">
-  <a class="text-{{:: item.style }}" target="{{:: item.target }}" href="{{:: displayUtils.getUrl(item.path, row) }}">
+  <a class="text-{{:: item.style }}" target="{{:: item.target }}" href="{{:: $ctrl.getUrl(item.path, row) }}">
     <i ng-if=":: item.icon" class="crm-i {{:: item.icon }}"></i>
     {{:: $ctrl.replaceTokens(item.text, row) }}
   </a>
index f4f9afb34053f215f248a750afc27fb4e3b8eb34..fa961dcf37996e67c114a59351545b716cf9d663 100644 (file)
@@ -5,7 +5,7 @@
   </button>
   <ul class="dropdown-menu {{ col.alignment === 'text-right' ? 'dropdown-menu-right' : '' }}" ng-if=":: col.open">
     <li ng-repeat="item in col.links" class="bg-{{:: item.style }}">
-      <a href="{{:: displayUtils.getUrl(item.path, row) }}" target="{{:: item.target }}">
+      <a href="{{:: $ctrl.getUrl(item.path, row) }}" target="{{:: item.target }}">
         <i ng-if=":: item.icon" class="crm-i {{:: item.icon }}"></i>
         {{:: $ctrl.replaceTokens(item.text, row) }}
       </a>
index e8137cba170a836ff58b43955f5f398819389900..718452d35ebcd16fed22ed594fc9510481f7ff18 100644 (file)
       afFieldset: '?^^afFieldset'
     },
     templateUrl: '~/crmSearchDisplayList/crmSearchDisplayList.html',
-    controller: function($scope, $element, crmApi4, searchDisplayUtils) {
+    controller: function($scope, $element, searchDisplayBaseTrait) {
       var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
-        ctrl = this;
-
-      this.page = 1;
-      this.rowCount = null;
+        // Mix in properties of searchDisplayBaseTrait
+        ctrl = angular.extend(this, searchDisplayBaseTrait);
 
       this.$onInit = function() {
-        this.sort = this.settings.sort ? _.cloneDeep(this.settings.sort) : [];
-        $scope.displayUtils = searchDisplayUtils;
-
-        // If search is embedded in contact summary tab, display count in tab-header
-        var contactTab = $element.closest('.crm-contact-page .ui-tabs-panel').attr('id');
-        if (contactTab) {
-          var unwatchCount = $scope.$watch('$ctrl.rowCount', function(rowCount) {
-            if (typeof rowCount === 'number') {
-              unwatchCount();
-              CRM.tabHeader.updateCount(contactTab.replace('contact-', '#tab_'), rowCount);
-            }
-          });
-        }
-
-        if (this.afFieldset) {
-          $scope.$watch(this.afFieldset.getFieldData, onChangeFilters, true);
-        }
-        $scope.$watch('$ctrl.filters', onChangeFilters, true);
+        this.initializeDisplay($scope, $element);
       };
 
-      this.getResults = _.debounce(function() {
-        searchDisplayUtils.getResults(ctrl);
-      }, 100);
-
       // Refresh current page
       this.refresh = function(row) {
-        searchDisplayUtils.getResults(ctrl);
-      };
-
-      function onChangeFilters() {
-        ctrl.page = 1;
-        ctrl.rowCount = null;
         ctrl.getResults();
-      }
-
-      this.formatFieldValue = function(rowData, col) {
-        return searchDisplayUtils.formatDisplayValue(rowData, col.key, ctrl.settings.columns);
-      };
-
-      this.replaceTokens = function(value, row, raw) {
-        return searchDisplayUtils.replaceTokens(value, row, raw ? null : ctrl.settings.columns);
-      };
-
-      this.getLinks = function(rowData, col) {
-        rowData._links = rowData._links || {};
-        if (!(col.key in rowData._links)) {
-          rowData._links[col.key] = searchDisplayUtils.formatLinks(rowData, col.key, ctrl.settings.columns);
-        }
-        return rowData._links[col.key];
       };
 
     }
index 59bae1aab8db509f4b62ddc58006b56863f788d1..50d063707413701b0bee840c3c347b21f020d26a 100644 (file)
@@ -1,7 +1,7 @@
 <li ng-repeat="(rowIndex, row) in $ctrl.results">
-  <div ng-repeat="col in $ctrl.settings.columns" title="{{:: displayUtils.replaceTokens(col.title, row, $ctrl.settings.columns) }}" class="{{:: col.break ? '' : 'crm-inline-block' }}">
+  <div ng-repeat="col in $ctrl.settings.columns" title="{{:: $ctrl.replaceTokens(col.title, row) }}" class="{{:: col.break ? '' : 'crm-inline-block' }}">
     <label ng-if=":: col.label && (col.type !== 'field' || col.forceLabel || row[col.key])">
-      {{:: displayUtils.replaceTokens(col.label, row, $ctrl.settings.columns) }}
+      {{:: $ctrl.replaceTokens(col.label, row) }}
     </label>
     <span ng-include="'~/crmSearchDisplay/colType/' + col.type + '.html'"></span>
   </div>
index 2d82221e9067fe6a311a2a2b981bb6ff74d5dd1f..c4ed51c7d0e8f4d823b9c56b6644d85914d0898c 100644 (file)
       afFieldset: '?^^afFieldset'
     },
     templateUrl: '~/crmSearchDisplayTable/crmSearchDisplayTable.html',
-    controller: function($scope, $element, crmApi4, searchDisplayUtils) {
+    controller: function($scope, $element, crmApi4, searchDisplayBaseTrait) {
       var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
-        ctrl = this;
+        // Mix in properties of searchDisplayBaseTrait
+        ctrl = angular.extend(this, searchDisplayBaseTrait);
 
-      this.page = 1;
-      this.rowCount = null;
       this.selectedRows = [];
       this.allRowsSelected = false;
 
       this.$onInit = function() {
-        this.sort = this.settings.sort ? _.cloneDeep(this.settings.sort) : [];
-        $scope.displayUtils = searchDisplayUtils;
-
-        // If search is embedded in contact summary tab, display count in tab-header
-        var contactTab = $element.closest('.crm-contact-page .ui-tabs-panel').attr('id');
-        if (contactTab) {
-          var unwatchCount = $scope.$watch('$ctrl.rowCount', function(rowCount) {
-            if (typeof rowCount === 'number') {
-              unwatchCount();
-              CRM.tabHeader.updateCount(contactTab.replace('contact-', '#tab_'), rowCount);
-            }
-          });
-        }
-
-        if (this.afFieldset) {
-          $scope.$watch(this.afFieldset.getFieldData, onChangeFilters, true);
-        }
-        $scope.$watch('$ctrl.filters', onChangeFilters, true);
+        this.initializeDisplay($scope, $element);
       };
 
-      this.getResults = _.debounce(function() {
-        searchDisplayUtils.getResults(ctrl);
-      }, 100);
-
       // Refresh page after inline-editing a row
       this.refresh = function(row) {
         var rowId = row.id;
-        searchDisplayUtils.getResults(ctrl)
+        ctrl.getResults()
           .then(function() {
             // If edited row disappears (because edits cause it to not meet search criteria), deselect it
             var index = ctrl.selectedRows.indexOf(rowId);
           });
       };
 
-      function onChangeFilters() {
-        ctrl.page = 1;
-        ctrl.rowCount = null;
+      this.onChangeFilters = function() {
         ctrl.selectedRows.legth = 0;
         ctrl.allRowsSelected = false;
-        ctrl.getResults();
-      }
+      };
 
       /**
        * Returns crm-i icon class for a sortable column
         } else {
           ctrl.sort.push([col.key, dir]);
         }
-        ctrl.getResults();
-      };
-
-      this.formatFieldValue = function(rowData, col) {
-        return searchDisplayUtils.formatDisplayValue(rowData, col.key, ctrl.settings.columns);
-      };
-
-      this.replaceTokens = function(value, row, raw) {
-        return searchDisplayUtils.replaceTokens(value, row, raw ? null : ctrl.settings.columns);
-      };
-
-      this.getLinks = function(rowData, col) {
-        rowData._links = rowData._links || {};
-        if (!(col.key in rowData._links)) {
-          rowData._links[col.key] = searchDisplayUtils.formatLinks(rowData, col.key, ctrl.settings.columns);
-        }
-        return rowData._links[col.key];
+        $scope.getResults();
       };
 
       $scope.selectAllRows = function() {
         }
         // If more than one page of results, use ajax to fetch all ids
         $scope.loadingAllRows = true;
-        var params = searchDisplayUtils.getApiParams(ctrl, 'id');
+        var params = ctrl.getApiParams('id');
         crmApi4('SearchDisplay', 'run', params, ['id']).then(function(ids) {
           $scope.loadingAllRows = false;
           ctrl.selectedRows = _.toArray(ids);
index 9cf6fcfdbd43e0de2a939be425847a5350466f7c..ba1eed9e13201931d0d689d3bf9ec184944fd4c1 100644 (file)
@@ -1,6 +1,6 @@
 <div class="crm-search-display crm-search-display-table">
   <div class="form-inline" ng-if="$ctrl.settings.actions">
-    <crm-search-tasks entity="$ctrl.apiEntity" ids="$ctrl.selectedRows" refresh="$ctrl.getResults()"></crm-search-tasks>
+    <crm-search-tasks entity="$ctrl.apiEntity" ids="$ctrl.selectedRows" refresh="getResults()"></crm-search-tasks>
   </div>
   <table>
     <thead>
@@ -19,7 +19,7 @@
         <td ng-if=":: $ctrl.settings.actions">
           <input type="checkbox" ng-checked="isRowSelected(row)" ng-click="selectRow(row)" ng-disabled="!(!loadingAllRows && row.id)">
         </td>
-        <td ng-repeat="col in $ctrl.settings.columns" ng-include="'~/crmSearchDisplay/colType/' + col.type + '.html'" title="{{:: displayUtils.replaceTokens(col.title, row, $ctrl.settings.columns) }}" class="{{:: col.alignment }}">
+        <td ng-repeat="col in $ctrl.settings.columns" ng-include="'~/crmSearchDisplay/colType/' + col.type + '.html'" title="{{:: $ctrl.replaceTokens(col.title, row) }}" class="{{:: col.alignment }}">
         </td>
         <td></td>
       </tr>