SearchKit - Refactor task-related functions into a trait and move base trait to its...
authorColeman Watts <coleman@civicrm.org>
Thu, 5 Aug 2021 16:41:46 +0000 (12:41 -0400)
committerColeman Watts <coleman@civicrm.org>
Sun, 8 Aug 2021 21:36:07 +0000 (17:36 -0400)
ext/search_kit/ang/crmSearchDisplay.module.js
ext/search_kit/ang/crmSearchDisplay/traits/searchDisplayBaseTrait.service.js [new file with mode: 0644]
ext/search_kit/ang/crmSearchDisplayTable/crmSearchDisplayTable.component.js
ext/search_kit/ang/crmSearchDisplayTable/crmSearchDisplayTable.html
ext/search_kit/ang/crmSearchTasks/traits/searchDisplayTasksTrait.service.js [new file with mode: 0644]

index 14b863f67b591d2efc6e632640960ae24dfb6d05..beae3e52e1259333bc0e8c84091bce7a1b6079d3 100644 (file)
@@ -2,166 +2,6 @@
   "use strict";
 
   // Declare module
-  angular.module('crmSearchDisplay', CRM.angRequires('crmSearchDisplay'))
-
-    // 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.
-      function replaceTokens(str, rowData, rowMeta, index) {
-        if (!str) {
-          return '';
-        }
-        _.each(rowData, function(value, key) {
-          if (str.indexOf('[' + key + ']') >= 0) {
-            var column = rowMeta && _.findWhere(rowMeta, {key: key}),
-              val = column ? formatRawValue(column, value) : value,
-              replacement = angular.isArray(val) ? val[index || 0] : val;
-            str = str.replace(new RegExp(_.escapeRegExp('[' + key + ']', 'g')), replacement);
-          }
-        });
-        return str;
-      }
-
-      function getUrl(link, rowData, index) {
-        var url = replaceTokens(link, rowData, null, index);
-        if (url.slice(0, 1) !== '/' && url.slice(0, 4) !== 'http') {
-          url = CRM.url(url);
-        }
-        return url;
-      }
-
-      // Returns display value for a single column in a row
-      function formatDisplayValue(rowData, key, columns) {
-        var column = _.findWhere(columns, {key: key}),
-          displayValue = column.rewrite ? replaceTokens(column.rewrite, rowData, columns) : formatRawValue(column, rowData[key]);
-        return angular.isArray(displayValue) ? displayValue.join(', ') : displayValue;
-      }
-
-      // Returns value and url for a column formatted as link(s)
-      function formatLinks(rowData, key, columns) {
-        var column = _.findWhere(columns, {key: key}),
-          value = formatRawValue(column, rowData[key]),
-          values = angular.isArray(value) ? value : [value],
-          links = [];
-        _.each(values, function(value, index) {
-          links.push({
-            value: value,
-            url: getUrl(column.link.path, rowData, index)
-          });
-        });
-        return links;
-      }
-
-      // Formats raw field value according to data type
-      function formatRawValue(column, value) {
-        var type = column && column.dataType,
-          result = value;
-        if (_.isArray(value)) {
-          return _.map(value, function(val) {
-            return formatRawValue(column, val);
-          });
-        }
-        if (value && (type === 'Date' || type === 'Timestamp') && /^\d{4}-\d{2}-\d{2}/.test(value)) {
-          result = CRM.utils.formatDate(value, null, type === 'Timestamp');
-        }
-        else if (type === 'Boolean' && typeof value === 'boolean') {
-          result = value ? ts('Yes') : ts('No');
-        }
-        else if (type === 'Money' && typeof value === 'number') {
-          result = CRM.formatMoney(value);
-        }
-        return result;
-      }
-
-      // 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);
-
-          // 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();
-          }
-
-          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);
-        }
-      };
-    });
+  angular.module('crmSearchDisplay', CRM.angRequires('crmSearchDisplay'));
 
 })(angular, CRM.$, CRM._);
diff --git a/ext/search_kit/ang/crmSearchDisplay/traits/searchDisplayBaseTrait.service.js b/ext/search_kit/ang/crmSearchDisplay/traits/searchDisplayBaseTrait.service.js
new file mode 100644 (file)
index 0000000..68d45d5
--- /dev/null
@@ -0,0 +1,164 @@
+(function(angular, $, _) {
+  "use strict";
+
+  // Trait provides base methods and properties common to all search display types
+  angular.module('crmSearchDisplay').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.
+    function replaceTokens(str, rowData, rowMeta, index) {
+      if (!str) {
+        return '';
+      }
+      _.each(rowData, function(value, key) {
+        if (str.indexOf('[' + key + ']') >= 0) {
+          var column = rowMeta && _.findWhere(rowMeta, {key: key}),
+            val = column ? formatRawValue(column, value) : value,
+            replacement = angular.isArray(val) ? val[index || 0] : val;
+          str = str.replace(new RegExp(_.escapeRegExp('[' + key + ']', 'g')), replacement);
+        }
+      });
+      return str;
+    }
+
+    function getUrl(link, rowData, index) {
+      var url = replaceTokens(link, rowData, null, index);
+      if (url.slice(0, 1) !== '/' && url.slice(0, 4) !== 'http') {
+        url = CRM.url(url);
+      }
+      return url;
+    }
+
+    // Returns display value for a single column in a row
+    function formatDisplayValue(rowData, key, columns) {
+      var column = _.findWhere(columns, {key: key}),
+        displayValue = column.rewrite ? replaceTokens(column.rewrite, rowData, columns) : formatRawValue(column, rowData[key]);
+      return angular.isArray(displayValue) ? displayValue.join(', ') : displayValue;
+    }
+
+    // Returns value and url for a column formatted as link(s)
+    function formatLinks(rowData, key, columns) {
+      var column = _.findWhere(columns, {key: key}),
+        value = formatRawValue(column, rowData[key]),
+        values = angular.isArray(value) ? value : [value],
+        links = [];
+      _.each(values, function(value, index) {
+        links.push({
+          value: value,
+          url: getUrl(column.link.path, rowData, index)
+        });
+      });
+      return links;
+    }
+
+    // Formats raw field value according to data type
+    function formatRawValue(column, value) {
+      var type = column && column.dataType,
+        result = value;
+      if (_.isArray(value)) {
+        return _.map(value, function(val) {
+          return formatRawValue(column, val);
+        });
+      }
+      if (value && (type === 'Date' || type === 'Timestamp') && /^\d{4}-\d{2}-\d{2}/.test(value)) {
+        result = CRM.utils.formatDate(value, null, type === 'Timestamp');
+      }
+      else if (type === 'Boolean' && typeof value === 'boolean') {
+        result = value ? ts('Yes') : ts('No');
+      }
+      else if (type === 'Money' && typeof value === 'number') {
+        result = CRM.formatMoney(value);
+      }
+      return result;
+    }
+
+    // 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);
+
+        // 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();
+        }
+
+        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);
+      }
+    };
+  });
+
+})(angular, CRM.$, CRM._);
index c4ed51c7d0e8f4d823b9c56b6644d85914d0898c..7801a41b0be442aed55f9c48304a74b8aca06c4c 100644 (file)
       afFieldset: '?^^afFieldset'
     },
     templateUrl: '~/crmSearchDisplayTable/crmSearchDisplayTable.html',
-    controller: function($scope, $element, crmApi4, searchDisplayBaseTrait) {
+    controller: function($scope, $element, crmApi4, searchDisplayBaseTrait, searchDisplayTasksTrait) {
       var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
-        // Mix in properties of searchDisplayBaseTrait
-        ctrl = angular.extend(this, searchDisplayBaseTrait);
-
-      this.selectedRows = [];
-      this.allRowsSelected = false;
+        // Mix in traits to this controller
+        ctrl = angular.extend(this, searchDisplayBaseTrait, searchDisplayTasksTrait);
 
       this.$onInit = function() {
         this.initializeDisplay($scope, $element);
           });
       };
 
-      this.onChangeFilters = function() {
-        ctrl.selectedRows.legth = 0;
-        ctrl.allRowsSelected = false;
-      };
-
       /**
        * Returns crm-i icon class for a sortable column
        * @param col
         $scope.getResults();
       };
 
-      $scope.selectAllRows = function() {
-        // Deselect all
-        if (ctrl.allRowsSelected) {
-          ctrl.allRowsSelected = false;
-          ctrl.selectedRows.length = 0;
-          return;
-        }
-        // Select all
-        ctrl.allRowsSelected = true;
-        if (ctrl.page === 1 && ctrl.results.length < ctrl.settings.limit) {
-          ctrl.selectedRows = _.pluck(ctrl.results, 'id');
-          return;
-        }
-        // If more than one page of results, use ajax to fetch all ids
-        $scope.loadingAllRows = true;
-        var params = ctrl.getApiParams('id');
-        crmApi4('SearchDisplay', 'run', params, ['id']).then(function(ids) {
-          $scope.loadingAllRows = false;
-          ctrl.selectedRows = _.toArray(ids);
-        });
-      };
-
-      $scope.selectRow = function(row) {
-        var index = ctrl.selectedRows.indexOf(row.id);
-        if (index < 0) {
-          ctrl.selectedRows.push(row.id);
-          ctrl.allRowsSelected = (ctrl.rowCount === ctrl.selectedRows.length);
-        } else {
-          ctrl.allRowsSelected = false;
-          ctrl.selectedRows.splice(index, 1);
-        }
-      };
-
-      $scope.isRowSelected = function(row) {
-        return ctrl.allRowsSelected || _.includes(ctrl.selectedRows, row.id);
-      };
-
     }
   });
 
index ba1eed9e13201931d0d689d3bf9ec184944fd4c1..9917eb082f72070a2612a367440a040e29990db0 100644 (file)
@@ -6,7 +6,7 @@
     <thead>
       <tr>
         <th class="crm-search-result-select" ng-if=":: $ctrl.settings.actions">
-          <input type="checkbox" ng-checked="$ctrl.allRowsSelected" ng-click="selectAllRows()" >
+          <input type="checkbox" ng-checked="$ctrl.allRowsSelected" ng-click="$ctrl.selectAllRows()" >
         </th>
         <th ng-repeat="col in $ctrl.settings.columns" ng-click="setSort(col, $event)" title="{{:: ts('Click to sort results (shift-click to sort by multiple).') }}">
           <i ng-if="col.type === 'field'" class="crm-i {{ getSort(col) }}"></i>
@@ -17,7 +17,7 @@
     <tbody>
       <tr ng-repeat="(rowIndex, row) in $ctrl.results">
         <td ng-if=":: $ctrl.settings.actions">
-          <input type="checkbox" ng-checked="isRowSelected(row)" ng-click="selectRow(row)" ng-disabled="!(!loadingAllRows && row.id)">
+          <input type="checkbox" ng-checked="$ctrl.isRowSelected(row)" ng-click="$ctrl.selectRow(row)" ng-disabled="!(!$ctrl.loadingAllRows && row.id)">
         </td>
         <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>
diff --git a/ext/search_kit/ang/crmSearchTasks/traits/searchDisplayTasksTrait.service.js b/ext/search_kit/ang/crmSearchTasks/traits/searchDisplayTasksTrait.service.js
new file mode 100644 (file)
index 0000000..bfbb4f9
--- /dev/null
@@ -0,0 +1,64 @@
+(function(angular, $, _) {
+  "use strict";
+
+  // Trait shared by any search display controllers which use tasks
+  angular.module('crmSearchDisplay').factory('searchDisplayTasksTrait', function(crmApi4) {
+    var ts = CRM.ts('org.civicrm.search_kit');
+
+    // Trait properties get mixed into display controller using angular.extend()
+    return {
+
+      selectedRows: [],
+      allRowsSelected: false,
+
+      // Toggle the "select all" checkbox
+      selectAllRows: function() {
+        var ctrl = this;
+        // Deselect all
+        if (ctrl.allRowsSelected) {
+          ctrl.allRowsSelected = false;
+          ctrl.selectedRows.length = 0;
+          return;
+        }
+        // Select all
+        ctrl.allRowsSelected = true;
+        if (ctrl.page === 1 && ctrl.results.length < ctrl.settings.limit) {
+          ctrl.selectedRows = _.pluck(ctrl.results, 'id');
+          return;
+        }
+        // If more than one page of results, use ajax to fetch all ids
+        ctrl.loadingAllRows = true;
+        var params = ctrl.getApiParams('id');
+        crmApi4('SearchDisplay', 'run', params, ['id']).then(function(ids) {
+          ctrl.loadingAllRows = false;
+          ctrl.selectedRows = _.toArray(ids);
+        });
+      },
+
+      // Toggle row selection
+      selectRow: function(row) {
+        var index = this.selectedRows.indexOf(row.id);
+        if (index < 0) {
+          this.selectedRows.push(row.id);
+          this.allRowsSelected = (this.rowCount === this.selectedRows.length);
+        } else {
+          this.allRowsSelected = false;
+          this.selectedRows.splice(index, 1);
+        }
+      },
+
+      // @return bool
+      isRowSelected: function(row) {
+        return this.allRowsSelected || _.includes(this.selectedRows, row.id);
+      },
+
+      // Reset selection when filters are changed
+      onChangeFilters: function() {
+        this.selectedRows.length = 0;
+        this.allRowsSelected = false;
+      }
+
+    };
+  });
+
+})(angular, CRM.$, CRM._);