Skip to content

Commit

Permalink
Merge pull request #22906 from colemanw/searchKitCheckBoxes
Browse files Browse the repository at this point in the history
SearchKit - Improve checkbox UX for bulk actions
  • Loading branch information
eileenmcnaughton authored Mar 14, 2022
2 parents 629bcbf + 7ef2c5c commit 5bd58e7
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
// Trait properties get mixed into display controller using angular.extend()
return {

sort: [],

isSortable: function(col) {
return !this.settings.draggable && col.type === 'field' && col.sortable !== false;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)" 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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,90 @@
// 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) {
var index = this.selectedRows.indexOf(row.key);
toggleRow: function(row, event) {
this.selectedRows = this.selectedRows || [];
var ctrl = this,
index = ctrl.selectedRows.indexOf(row.key);

// See if any boxes are checked above/below this one
function checkRange(allRows, checkboxPosition, dir) {
for (var row = checkboxPosition; row >= 0 && row <= allRows.length; row += dir) {
if (ctrl.selectedRows.indexOf(allRows[row]) > -1) {
return row;
}
}
}

// Check a bunch of boxes
function selectRange(allRows, start, end) {
for (var row = start; row <= end; ++row) {
ctrl.selectedRows.push(allRows[row]);
}
}

if (index < 0) {
this.selectedRows.push(row.key);
this.allRowsSelected = (this.rowCount === this.selectedRows.length);
// Shift-click - select range between clicked checkbox and the nearest selected row
if (event.shiftKey && ctrl.selectedRows.length) {
var allRows = _.pluck(ctrl.results, 'key'),
checkboxPosition = allRows.indexOf(row.key);

var nearestBefore = checkRange(allRows, checkboxPosition, -1),
nearestAfter = checkRange(allRows, checkboxPosition, 1);

// Select range between clicked box and the previous/next checked box
// In the ambiguous situation where there are checked boxes both above AND below the clicked box,
// choose the direction of the box which was most recently clicked.
if (nearestAfter !== undefined && (nearestBefore === undefined || nearestAfter === allRows.indexOf(_.last(ctrl.selectedRows)))) {
selectRange(allRows, checkboxPosition + 1, nearestAfter - 1);
} else if (nearestBefore !== undefined && (nearestAfter === undefined || nearestBefore === allRows.indexOf(_.last(ctrl.selectedRows)))) {
selectRange(allRows, nearestBefore + 1, checkboxPosition -1);
}
}
ctrl.selectedRows = _.uniq(ctrl.selectedRows.concat([row.key]));
ctrl.allRowsSelected = (ctrl.rowCount === ctrl.selectedRows.length);
} else {
this.allRowsSelected = false;
this.selectedRows.splice(index, 1);
ctrl.allRowsSelected = false;
ctrl.selectedRows.splice(index, 1);
}
},

Expand All @@ -52,8 +100,13 @@
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();
Expand All @@ -62,13 +115,13 @@
// 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})) {
Expand Down
4 changes: 0 additions & 4 deletions ext/search_kit/css/crmSearchAdmin.css
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,6 @@
height: 36px;
}

#bootstrap-theme.crm-search th.crm-search-result-select {
padding-right: 10px;
}

#bootstrap-theme .crm-search-delete-display {
position: absolute;
right: 0;
Expand Down
7 changes: 6 additions & 1 deletion ext/search_kit/css/crmSearchTasks.css
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 5bd58e7

Please sign in to comment.