SearchKit - Add menu for selecting all/page/none
authorColeman Watts <coleman@civicrm.org>
Mon, 7 Mar 2022 19:30:51 +0000 (14:30 -0500)
committerColeman Watts <coleman@civicrm.org>
Wed, 9 Mar 2022 20:02:36 +0000 (15:02 -0500)
ext/search_kit/ang/crmSearchAdmin/resultsTable/crmSearchAdminResultsTable.html
ext/search_kit/ang/crmSearchDisplayTable/crmSearchDisplayTable.html
ext/search_kit/ang/crmSearchDisplayTable/crmSearchDisplayTableBody.html
ext/search_kit/ang/crmSearchDisplayTable/crmSearchDisplayTaskHeader.html [new file with mode: 0644]
ext/search_kit/ang/crmSearchTasks/traits/searchDisplayTasksTrait.service.js
ext/search_kit/css/crmSearchAdmin.css
ext/search_kit/css/crmSearchTasks.css

index aff0c96b795aa14c3f3af53cb5311a78eda0d24e..31e8b6f0e8632df551e364a2016a7c432075cca2 100644 (file)
@@ -7,8 +7,7 @@
   <table class="{{:: $ctrl.settings.classes.join(' ') }}">
     <thead>
       <tr ng-model="$ctrl.search.api_params.select" ui-sortable="sortableColumnOptions">
-        <th class="crm-search-result-select" ng-if=":: $ctrl.settings.actions">
-          <input type="checkbox" ng-disabled="$ctrl.loading || !$ctrl.results.length" ng-checked="$ctrl.allRowsSelected" ng-click="$ctrl.selectAllRows()" >
+        <th class="crm-search-result-select" ng-include="'~/crmSearchDisplayTable/crmSearchDisplayTaskHeader.html'" ng-if=":: $ctrl.settings.actions">
         </th>
         <th ng-repeat="item in $ctrl.search.api_params.select" ng-click="$ctrl.setSort($ctrl.settings.columns[$index], $event)" title="{{$index || !$ctrl.crmSearchAdmin.groupExists ? ts('Drag to reorder columns, click to sort results (shift-click to sort by multiple).') : ts('Column reserved for smart group.')}}">
           <i ng-if=":: $ctrl.isSortable($ctrl.settings.columns[$index])" class="crm-i {{ $ctrl.getSort($ctrl.settings.columns[$index]) }}"></i>
index 2731e1f056816a3ce263d4d5ae5dd6bdac7d7710..0293a9294f479ccc47d8963f68847ab0f12c9159 100644 (file)
@@ -6,9 +6,7 @@
   <table class="{{:: $ctrl.settings.classes.join(' ') }}">
     <thead>
       <tr>
-        <th class="crm-search-result-select" ng-if=":: $ctrl.settings.actions || $ctrl.settings.draggable">
-          <i ng-if=":: $ctrl.settings.draggable" class="crm-i fa-sort-amount-asc" title="{{:: ts('Drag columns to reposition') }}"></i>
-          <input type="checkbox" ng-if=":: $ctrl.settings.actions" ng-disabled="$ctrl.loading || !$ctrl.results.length" ng-checked="$ctrl.allRowsSelected" ng-click="$ctrl.selectAllRows()" >
+        <th ng-class="{'crm-search-result-select': $ctrl.settings.actions}" ng-include="'~/crmSearchDisplayTable/crmSearchDisplayTaskHeader.html'" ng-if=":: $ctrl.settings.actions || $ctrl.settings.draggable">
         </th>
         <th ng-repeat="col in $ctrl.settings.columns" ng-click="$ctrl.setSort(col, $event)" class="{{:: $ctrl.isSortable(col) ? 'crm-sortable-col' : ''}}" title="{{:: $ctrl.isSortable(col) ? ts('Click to sort results (shift-click to sort by multiple).') : '' }}">
           <i ng-if=":: $ctrl.isSortable(col)" class="crm-i {{ $ctrl.getSort(col) }}"></i>
index 8dcb6a27986264bd067195d47f82c14cc43a0b1a..36aba9cd4e310b88e0d587dc08a0f70c14f0bc45 100644 (file)
@@ -3,7 +3,7 @@
     <span ng-if=":: $ctrl.settings.draggable" class="crm-draggable" title="{{:: ts('Drag to reposition') }}">
       <i class="crm-i fa-arrows-v"></i>
     </span>
-    <input ng-if=":: $ctrl.settings.actions" type="checkbox" ng-checked="$ctrl.isRowSelected(row)" ng-click="$ctrl.selectRow(row, $event)" ng-disabled="!!$ctrl.loadingAllRows">
+    <input ng-if=":: $ctrl.settings.actions" type="checkbox" ng-checked="$ctrl.isRowSelected(row)" ng-click="$ctrl.toggleRow(row, $event)" ng-disabled="!!$ctrl.loadingAllRows">
   </td>
   <td ng-repeat="(colIndex, colData) in row.columns" ng-include="'~/crmSearchDisplay/colType/' + $ctrl.settings.columns[colIndex].type + '.html'" title="{{:: colData.title }}" class="{{:: row.cssClass }} {{:: colData.cssClass }}">
   </td>
diff --git a/ext/search_kit/ang/crmSearchDisplayTable/crmSearchDisplayTaskHeader.html b/ext/search_kit/ang/crmSearchDisplayTable/crmSearchDisplayTaskHeader.html
new file mode 100644 (file)
index 0000000..7cd2216
--- /dev/null
@@ -0,0 +1,26 @@
+<i ng-if=":: $ctrl.settings.draggable" class="crm-i fa-sort-amount-asc" title="{{:: ts('Drag columns to reposition') }}"></i>
+<div class="btn-group" ng-if=":: $ctrl.settings.actions">
+  <button type="button" class="btn btn-secondary-outline" ng-click="$ctrl.toggleAllRows()" ng-disabled="$ctrl.loading || !$ctrl.results.length" title="{{ $ctrl.selectedRows.length ? ts('Select none') : ts('Select all') }}">
+    <i class="crm-i" ng-class="{'fa-square-o': !$ctrl.selectedRows.length, 'fa-minus-square-o': !$ctrl.allRowsSelected && $ctrl.selectedRows.length, 'fa-check-square-o': $ctrl.allRowsSelected}"></i>
+  </button>
+  <button type="button" class="btn btn-secondary-outline dropdown-toggle" ng-click="$ctrl.selectAllMenuOpen = true;" ng-disabled="$ctrl.loading || !$ctrl.results.length" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+    <span class="caret"></span>
+  </button>
+  <ul class="dropdown-menu" ng-if="$ctrl.selectAllMenuOpen">
+    <li>
+      <a href ng-click="$ctrl.selectNone()">
+        {{:: ts('None') }}
+      </a>
+    </li>
+    <li>
+      <a href ng-click="$ctrl.selectPage()">
+        {{ $ctrl.rowCount > $ctrl.results.length ? ts('This Page') : ts('All') }}
+      </a>
+    </li>
+    <li ng-if="$ctrl.rowCount > $ctrl.results.length">
+      <a href ng-click="$ctrl.selectAllPages()">
+        {{:: ts('All Pages') }}
+      </a>
+    </li>
+  </ul>
+</div>
index c93167a8977ae177b5c1976149179980a2176c54..b15c67d8e732436b212e8f92451c2940d1d058ed 100644 (file)
@@ -8,35 +8,48 @@
     // Trait properties get mixed into display controller using angular.extend()
     return {
 
-      selectedRows: [],
-      allRowsSelected: false,
+      // Use ajax to select all rows on every page
+      selectAllPages: function() {
+        var ctrl = this;
+        ctrl.loadingAllRows = ctrl.allRowsSelected = true;
+        var params = ctrl.getApiParams('id');
+        crmApi4('SearchDisplay', 'run', params).then(function(ids) {
+          ctrl.loadingAllRows = false;
+          ctrl.selectedRows = _.uniq(_.toArray(ids));
+        });
+      },
+
+      // Select all rows on the current page
+      selectPage: function() {
+        this.allRowsSelected = true;
+        this.selectedRows = _.uniq(_.pluck(this.results, 'key'));
+      },
+
+      // Clear selection
+      selectNone: function() {
+        this.allRowsSelected = false;
+        this.selectedRows = [];
+      },
 
       // Toggle the "select all" checkbox
-      selectAllRows: function() {
-        var ctrl = this;
+      toggleAllRows: function() {
         // Deselect all
-        if (ctrl.allRowsSelected) {
-          ctrl.allRowsSelected = false;
-          ctrl.selectedRows.length = 0;
-          return;
+        if (this.selectedRows && this.selectedRows.length) {
+          this.selectNone();
         }
         // Select all
-        ctrl.allRowsSelected = true;
-        if (ctrl.page === 1 && ctrl.results.length < ctrl.limit) {
-          ctrl.selectedRows = _.pluck(ctrl.results, 'key');
-          return;
+        else if (this.page === 1 && this.rowCount === this.results.length) {
+          this.selectPage();
         }
         // 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).then(function(ids) {
-          ctrl.loadingAllRows = false;
-          ctrl.selectedRows = _.toArray(ids);
-        });
+        else {
+          this.selectAllPages();
+        }
       },
 
       // Toggle row selection
-      selectRow: function(row, event) {
+      toggleRow: function(row, event) {
+        this.selectedRows = this.selectedRows || [];
         var ctrl = this,
           index = ctrl.selectedRows.indexOf(row.key);
 
@@ -74,7 +87,7 @@
               selectRange(allRows, nearestBefore + 1, checkboxPosition -1);
             }
           }
-          ctrl.selectedRows.push(row.key);
+          ctrl.selectedRows = _.uniq(ctrl.selectedRows.concat([row.key]));
           ctrl.allRowsSelected = (ctrl.rowCount === ctrl.selectedRows.length);
         } else {
           ctrl.allRowsSelected = false;
         return this.allRowsSelected || _.includes(this.selectedRows, row.key);
       },
 
+      isPageSelected: function() {
+        return (this.allRowsSelected && this.rowCount === this.results.length) ||
+          (!this.allRowsSelected && this.selectedRows && this.selectedRows.length === this.results.length);
+      },
+
       refreshAfterTask: function() {
-        this.selectedRows.length = 0;
+        this.selectedRows = [];
         this.allRowsSelected = false;
         this.rowCount = undefined;
         this.runSearch();
       // Add onChangeFilters callback (gets merged with others via angular.extend)
       onChangeFilters: [function() {
         // Reset selection when filters are changed
-        this.selectedRows.length = 0;
+        this.selectedRows = [];
         this.allRowsSelected = false;
       }],
 
       // Add onPostRun callback (gets merged with others via angular.extend)
       onPostRun: [function(results, status, editedRow) {
-        if (editedRow && status === 'success') {
+        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})) {
index f69558afdc1701bd074a5054656878935faaba41..b5963b44d330913dbaef01846dd20acfd6079724 100644 (file)
   height: 36px;
 }
 
-#bootstrap-theme.crm-search th.crm-search-result-select {
-  padding-right: 10px;
-}
-
 #bootstrap-theme .crm-search-delete-display {
   position: absolute;
   right: 0;
index 89fada1d5909697343b820e7a8f22db270184be5..8a96d12a5f2f868e770f04d75a95acdbab139a55 100644 (file)
@@ -5,7 +5,12 @@
 }
 
 #bootstrap-theme .crm-search-display-table > table.table > thead > tr > th.crm-search-result-select {
-  vertical-align: middle;
+  padding-left: 0;
+  padding-right: 0;
+  text-transform: none;
+  color: initial;
+  /* Don't allow button to be split on 2 lines */
+  min-width: 86px;
 }
 
 .crm-search-display.crm-search-display-table td > crm-search-display-editable,